Merge pull request #17 from nbenaglia/feature/css-qmail-page
Apply dark/light theme in chat pages
265
src/App.tsx
@ -34,9 +34,9 @@ import ltcLogo from './assets/ltc.png';
|
||||
import PersonSearchIcon from '@mui/icons-material/PersonSearch';
|
||||
import qortLogo from './assets/qort.png';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { Download } from './assets/svgs/Download.tsx';
|
||||
import { Logout } from './assets/svgs/Logout.tsx';
|
||||
import { Return } from './assets/svgs/Return.tsx';
|
||||
import { Download } from './assets/Icons/Download.tsx';
|
||||
import { Logout } from './assets/Icons/Logout.tsx';
|
||||
import { Return } from './assets/Icons/Return.tsx';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import Success from './assets/svgs/Success.svg';
|
||||
import './utils/seedPhrase/RandomSentenceGenerator';
|
||||
@ -69,13 +69,11 @@ import { Spacer } from './common/Spacer';
|
||||
import { Loader } from './components/Loader';
|
||||
import { PasswordField, ErrorText } from './components';
|
||||
import { Group, requestQueueMemberNames } from './components/Group/Group';
|
||||
import { TaskManager } from './components/TaskManager/TaskManger';
|
||||
import { TaskManager } from './components/TaskManager/TaskManager.tsx';
|
||||
import { useModal } from './common/useModal';
|
||||
import { Label } from './components/Group/AddGroup';
|
||||
import { CustomizedSnackbars } from './components/Snackbar/Snackbar';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import HelpIcon from '@mui/icons-material/Help';
|
||||
|
||||
import {
|
||||
cleanUrl,
|
||||
getProtocol,
|
||||
@ -119,11 +117,6 @@ import {
|
||||
} from './atoms/global';
|
||||
import { useAppFullScreen } from './useAppFullscreen';
|
||||
import { NotAuthenticated } from './ExtStates/NotAuthenticated';
|
||||
import {
|
||||
openIndexedDB,
|
||||
showSaveFilePicker,
|
||||
} from './components/Apps/useQortalMessageListener';
|
||||
import { fileToBase64 } from './utils/fileReading';
|
||||
import { handleGetFileFromIndexedDB } from './utils/indexedDB';
|
||||
import { CoreSyncStatus } from './components/CoreSyncStatus';
|
||||
import { Wallets } from './Wallets';
|
||||
@ -131,7 +124,6 @@ import { RandomSentenceGenerator } from './utils/seedPhrase/RandomSentenceGenera
|
||||
import { useFetchResources } from './common/useFetchResources';
|
||||
import { Tutorials } from './components/Tutorials/Tutorials';
|
||||
import { useHandleTutorials } from './components/Tutorials/useHandleTutorials';
|
||||
import BoundedNumericTextField from './common/BoundedNumericTextField';
|
||||
import { useHandleUserInfo } from './components/Group/useHandleUserInfo';
|
||||
import { Minting } from './components/Minting/Minting';
|
||||
import { isRunningGateway } from './qortalRequests';
|
||||
@ -139,7 +131,6 @@ import { QMailStatus } from './components/QMailStatus';
|
||||
import { GlobalActions } from './components/GlobalActions/GlobalActions';
|
||||
import { useBlockedAddresses } from './components/Group/useBlockUsers';
|
||||
import { WalletIcon } from './assets/Icons/WalletIcon';
|
||||
import { DrawerUserLookup } from './components/Drawer/DrawerUserLookup';
|
||||
import { UserLookup } from './components/UserLookup.tsx/UserLookup';
|
||||
import { RegisterName } from './components/RegisterName';
|
||||
import { BuyQortInformation } from './components/BuyQortInformation';
|
||||
@ -626,7 +617,9 @@ function App() {
|
||||
setIsDisabledEditorEnter(parsedVal);
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -684,7 +677,9 @@ function App() {
|
||||
try {
|
||||
if (typeof fileContents !== 'string') return;
|
||||
pf = JSON.parse(fileContents);
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const requiredFields = [
|
||||
@ -932,7 +927,9 @@ function App() {
|
||||
});
|
||||
|
||||
getBalanceFunc();
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -985,7 +982,6 @@ function App() {
|
||||
);
|
||||
} catch (error: any) {
|
||||
setWalletToBeDownloadedError(error?.message);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
@ -1107,7 +1103,9 @@ function App() {
|
||||
error.message || 'An error occurred'
|
||||
);
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const returnToMain = () => {
|
||||
@ -1333,9 +1331,7 @@ function App() {
|
||||
{authenticatedMode === 'qort' && (
|
||||
<Tooltip
|
||||
title={
|
||||
<span
|
||||
style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}
|
||||
>
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
LITECOIN WALLET
|
||||
</span>
|
||||
}
|
||||
@ -1345,13 +1341,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1372,9 +1368,7 @@ function App() {
|
||||
{authenticatedMode === 'ltc' && (
|
||||
<Tooltip
|
||||
title={
|
||||
<span
|
||||
style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}
|
||||
>
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
QORTAL WALLET
|
||||
</span>
|
||||
}
|
||||
@ -1384,13 +1378,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1435,10 +1429,10 @@ function App() {
|
||||
>
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
lineHeight: '24px',
|
||||
fontSize: '20px',
|
||||
fontWeight: 700,
|
||||
lineHeight: '24px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{ltcBalance} LTC
|
||||
@ -1447,7 +1441,6 @@ function App() {
|
||||
onClick={getLtcBalanceFunc}
|
||||
sx={{
|
||||
fontSize: '16px',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
@ -1494,10 +1487,10 @@ function App() {
|
||||
>
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
lineHeight: '24px',
|
||||
fontSize: '20px',
|
||||
fontWeight: 700,
|
||||
lineHeight: '24px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{balance?.toFixed(2)} QORT
|
||||
@ -1506,7 +1499,6 @@ function App() {
|
||||
onClick={getBalanceFunc}
|
||||
sx={{
|
||||
fontSize: '16px',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
@ -1517,13 +1509,13 @@ function App() {
|
||||
{userInfo && !userInfo?.name && (
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
lineHeight: 1.2,
|
||||
color: 'red',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
lineHeight: 1.2,
|
||||
marginTop: '10px',
|
||||
color: 'red',
|
||||
textAlign: 'center',
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
onClick={() => {
|
||||
@ -1548,12 +1540,12 @@ function App() {
|
||||
)}
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
lineHeight: '24px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
lineHeight: '24px',
|
||||
marginTop: '10px',
|
||||
textAlign: 'center',
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
onClick={async () => {
|
||||
@ -1573,18 +1565,18 @@ function App() {
|
||||
return (
|
||||
<AuthenticatedContainer
|
||||
sx={{
|
||||
width: isMobile ? '100vw' : 'auto',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
justifyContent: 'flex-end',
|
||||
width: isMobile ? '100vw' : 'auto',
|
||||
}}
|
||||
>
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
padding: '10px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
@ -1593,7 +1585,6 @@ function App() {
|
||||
}}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@ -1610,10 +1601,10 @@ function App() {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Spacer height="20px" />
|
||||
@ -1625,7 +1616,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1639,13 +1629,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1664,6 +1654,7 @@ function App() {
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<ButtonBase
|
||||
@ -1675,7 +1666,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1689,13 +1679,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1703,7 +1693,9 @@ function App() {
|
||||
<SettingsIcon />
|
||||
</Tooltip>
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setIsOpenDrawerLookup(true);
|
||||
@ -1713,7 +1705,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1727,13 +1718,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1741,7 +1732,9 @@ function App() {
|
||||
<PersonSearchIcon />
|
||||
</Tooltip>
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent('openWalletsApp', {});
|
||||
@ -1751,7 +1744,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1765,13 +1757,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1788,7 +1780,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1802,13 +1793,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1825,20 +1816,21 @@ function App() {
|
||||
)}
|
||||
|
||||
<Spacer height="20px" />
|
||||
<CoreSyncStatus />
|
||||
<Spacer height="20px" />
|
||||
|
||||
<QMailStatus />
|
||||
|
||||
<Spacer height="20px" />
|
||||
{extState === 'authenticated' && (
|
||||
<GeneralNotifications address={userInfo?.address} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{extState === 'authenticated' && isMainWindow && (
|
||||
@ -1873,7 +1865,9 @@ function App() {
|
||||
<GlobalActions memberGroups={memberGroups} />
|
||||
</MyContext.Provider>
|
||||
)}
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<ButtonBase
|
||||
onClick={async () => {
|
||||
try {
|
||||
@ -1896,7 +1890,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1910,13 +1903,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1926,6 +1919,7 @@ function App() {
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
{(desktopViewMode === 'apps' || desktopViewMode === 'home') && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
@ -1940,7 +1934,6 @@ function App() {
|
||||
title={
|
||||
<span
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
@ -1954,13 +1947,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -1973,9 +1966,7 @@ function App() {
|
||||
<Spacer height="20px" />
|
||||
<Tooltip
|
||||
title={
|
||||
<span
|
||||
style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}
|
||||
>
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
BACKUP WALLET
|
||||
</span>
|
||||
}
|
||||
@ -1985,13 +1976,13 @@ function App() {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -2113,7 +2104,7 @@ function App() {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
background: '#27282c',
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
@ -2403,17 +2394,7 @@ function App() {
|
||||
)}
|
||||
{` ${requestBuyOrder?.crosschainAtInfo?.[0]?.foreignBlockchain}`}
|
||||
</TextP>
|
||||
{/* <Spacer height="29px" />
|
||||
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
Confirm Wallet Password
|
||||
</CustomLabel>
|
||||
<Spacer height="5px" />
|
||||
<PasswordField
|
||||
id="standard-adornment-password"
|
||||
value={paymentPassword}
|
||||
onChange={(e) => setPaymentPassword(e.target.value)}
|
||||
/> */}
|
||||
<Spacer height="29px" />
|
||||
<Box
|
||||
sx={{
|
||||
@ -2592,6 +2573,7 @@ function App() {
|
||||
<img src={Logo1Dark} className="base-image" />
|
||||
</div>
|
||||
<Spacer height="38px" />
|
||||
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
@ -2602,7 +2584,9 @@ function App() {
|
||||
<TextItalic>{requestConnection?.hostname}</TextItalic> <br></br>
|
||||
<TextSpan>requests authentication</TextSpan>
|
||||
</TextP>
|
||||
|
||||
<Spacer height="38px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -2610,12 +2594,16 @@ function App() {
|
||||
gap: '14px',
|
||||
}}
|
||||
></Box>
|
||||
|
||||
<Spacer height="38px" />
|
||||
|
||||
<CustomButton {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
Authenticate
|
||||
</CustomButton>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setExtstate('create-wallet');
|
||||
@ -2628,14 +2616,15 @@ function App() {
|
||||
{extState === 'wallets' && (
|
||||
<>
|
||||
<Spacer height="22px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '22px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxWidth: '700px',
|
||||
paddingLeft: '22px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Return
|
||||
@ -2650,6 +2639,7 @@ function App() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Wallets
|
||||
setRawWallet={setRawWallet}
|
||||
setExtState={setExtstate}
|
||||
@ -2662,12 +2652,12 @@ function App() {
|
||||
<Spacer height="22px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '22px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxWidth: '700px',
|
||||
paddingLeft: '22px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Return
|
||||
@ -2703,7 +2693,9 @@ function App() {
|
||||
<Typography>
|
||||
{rawWallet?.name ? rawWallet?.name : rawWallet?.address0}
|
||||
</Typography>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'start',
|
||||
@ -2715,13 +2707,16 @@ function App() {
|
||||
Authenticate
|
||||
</TextP>
|
||||
</Box>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<>
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
Wallet Password
|
||||
</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<PasswordField
|
||||
id="standard-adornment-password"
|
||||
value={authenticatePassword}
|
||||
@ -2758,9 +2753,11 @@ function App() {
|
||||
)}
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<CustomButton onClick={authenticateWallet}>
|
||||
Authenticate
|
||||
</CustomButton>
|
||||
|
||||
<ErrorText>{walletToBeDecryptedError}</ErrorText>
|
||||
</>
|
||||
</>
|
||||
@ -2770,12 +2767,12 @@ function App() {
|
||||
<Spacer height="22px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '22px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxWidth: '700px',
|
||||
paddingLeft: '22px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Return
|
||||
@ -2796,7 +2793,9 @@ function App() {
|
||||
>
|
||||
<img src={Logo1Dark} className="base-image" />
|
||||
</div>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -2815,7 +2814,9 @@ function App() {
|
||||
Download Account
|
||||
</TextP>
|
||||
</Box>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
{!walletToBeDownloaded && (
|
||||
<>
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
@ -2858,14 +2859,15 @@ function App() {
|
||||
{!walletToBeDownloaded && (
|
||||
<>
|
||||
<Spacer height="22px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '22px',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
maxWidth: '700px',
|
||||
paddingLeft: '22px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Return
|
||||
@ -2966,7 +2968,6 @@ function App() {
|
||||
sx={{
|
||||
fontSize: '18px',
|
||||
marginTop: '15px',
|
||||
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
@ -3007,11 +3008,11 @@ function App() {
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
maxWidth: '400px',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
display: showSeed ? 'flex' : 'none',
|
||||
flexDirection: 'column',
|
||||
gap: '10px',
|
||||
maxWidth: '400px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -3024,11 +3025,11 @@ function App() {
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
backgroundColor: '#1f2023',
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: '5px',
|
||||
padding: '10px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{generatorRef.current?.parsedString}
|
||||
@ -3055,6 +3056,7 @@ function App() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: creationStep === 2 ? 'flex' : 'none',
|
||||
@ -3063,10 +3065,13 @@ function App() {
|
||||
}}
|
||||
>
|
||||
<Spacer height="14px" />
|
||||
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
Wallet Password
|
||||
</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<PasswordField
|
||||
id="standard-adornment-password"
|
||||
value={walletToBeDownloadedPassword}
|
||||
@ -3074,11 +3079,15 @@ function App() {
|
||||
setWalletToBeDownloadedPassword(e.target.value)
|
||||
}
|
||||
/>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
Confirm Wallet Password
|
||||
</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<PasswordField
|
||||
id="standard-adornment-password"
|
||||
value={walletToBeDownloadedPasswordConfirm}
|
||||
@ -3087,9 +3096,11 @@ function App() {
|
||||
}
|
||||
/>
|
||||
<Spacer height="5px" />
|
||||
|
||||
<Typography variant="body2">
|
||||
There is no minimum length requirement
|
||||
</Typography>
|
||||
|
||||
<Spacer height="17px" />
|
||||
|
||||
<CustomButton onClick={createAccountFunc}>
|
||||
@ -3146,13 +3157,13 @@ function App() {
|
||||
{isOpenSendQortSuccess && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
background: '#27282c',
|
||||
alignItems: 'center',
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
width: '100%',
|
||||
zIndex: 10000,
|
||||
}}
|
||||
>
|
||||
@ -3279,7 +3290,7 @@ function App() {
|
||||
<Button
|
||||
sx={{
|
||||
backgroundColor: 'var(--green)',
|
||||
color: 'black',
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
'&:hover': {
|
||||
|
@ -657,7 +657,6 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
}}
|
||||
disabled={false}
|
||||
defaultChecked
|
||||
/>
|
||||
}
|
||||
label={`Use ${isLocal ? 'Local' : 'Custom'} Node`}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Input,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { CustomButton } from './styles/App-styles';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
@ -266,6 +267,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
|
||||
onClick={handleSetSeedValue}
|
||||
sx={{
|
||||
padding: '10px',
|
||||
display: 'inline',
|
||||
}}
|
||||
>
|
||||
Add seed-phrase
|
||||
@ -401,6 +403,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||
const [name, setName] = useState('');
|
||||
const [note, setNote] = useState('');
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet?.name) {
|
||||
@ -423,10 +426,10 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||
>
|
||||
<ListItem
|
||||
sx={{
|
||||
bgcolor: 'background.paper', // TODO: set background color
|
||||
bgcolor: theme.palette.background.default,
|
||||
flexGrow: 1,
|
||||
'&:hover': {
|
||||
backgroundColor: 'secondary.main',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
transform: 'scale(1.01)',
|
||||
},
|
||||
transition: 'all 0.1s ease-in-out',
|
||||
@ -454,7 +457,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||
<Typography
|
||||
component="span"
|
||||
variant="body2"
|
||||
sx={{ color: 'text.primary', display: 'inline' }}
|
||||
sx={{ color: theme.palette.text.primary, display: 'inline' }}
|
||||
>
|
||||
{wallet?.address0}
|
||||
</Typography>
|
||||
@ -471,6 +474,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<IconButton
|
||||
sx={{
|
||||
alignSelf: 'flex-start',
|
||||
@ -482,11 +486,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||
edge="end"
|
||||
aria-label="edit"
|
||||
>
|
||||
<EditIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</ButtonBase>
|
||||
{isEdit && (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
export const AppsIcon = ({ height = 31, width = 31 }) => {
|
||||
const theme = useTheme();
|
||||
|
@ -21,8 +21,8 @@ export const Download: React.FC<SVGProps> = ({
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.8047 0.393196V7.21185H16.3036L10.0003 13.5139L3.69697 7.21185H7.19584V0H12.8045L12.8047 0.393196ZM2.7047 16.8587V13.9861H0V18.6179C0 19.3774 0.622589 20 1.38213 20H18.6179C19.3774 20 20 19.3774 20 18.6179V13.9861H17.2962V17.2963L2.70461 17.2954L2.7047 16.8587Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
@ -1,13 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
export const ExitIcon= ({ color = 'white', height, width }) => {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z" fill={color}/>
|
||||
</svg>
|
||||
|
||||
export const ExitIcon = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z"
|
||||
fill={theme.palette.text.primary}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
export const HomeIcon = ({ height = 20, width = 23 }) => {
|
||||
const theme = useTheme();
|
||||
|
@ -17,8 +17,8 @@ export const Logout: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.56485 0H16.3611C17.2662 0 18 0.727797 18 1.62558V18.3744C18 19.2722 17.2662 20 16.3611 20H7.56485C6.65969 20 5.92593 19.2722 5.92593 18.3744V12.6013H10.6168C11.4569 12.6013 12.1404 11.9039 12.1404 11.0467V8.87329C12.1404 8.01613 11.4569 7.31875 10.6168 7.31875H5.92593V1.62558C5.92593 0.727797 6.65969 0 7.56485 0ZM11.1667 11.0467C11.1667 11.3719 10.9205 11.6354 10.6168 11.6354H4.8144C4.74521 11.6354 4.68911 11.6955 4.68911 11.7696V12.8632C4.68911 13.3492 4.17007 13.6259 3.8078 13.3329L0.218431 10.4298C-0.0728102 10.1942 -0.0728102 9.72579 0.218431 9.49024L3.8078 6.58709C4.17005 6.29409 4.68911 6.57077 4.68911 7.05684V8.1504C4.68911 8.2245 4.74521 8.28454 4.8144 8.28454H10.6168C10.9205 8.28454 11.1667 8.54813 11.1667 8.87329V11.0467Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
@ -1,12 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export const LogoutIcon= ({ color, height = 20, width = 18}) => {
|
||||
return (
|
||||
<svg width={width} height={height} viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M10.4351 0H1.63891C0.733765 0 0 0.727797 0 1.62558V18.3744C0 19.2722 0.733765 20 1.63891 20H10.4351C11.3403 20 12.0741 19.2722 12.0741 18.3744V12.6013H7.38321C6.54312 12.6013 5.85964 11.9039 5.85964 11.0467V8.87329C5.85964 8.01613 6.54312 7.31875 7.38323 7.31875H12.0741V1.62558C12.0741 0.727797 11.3403 0 10.4351 0ZM6.83334 11.0467C6.83334 11.3719 7.07952 11.6354 7.38321 11.6354H13.1856C13.2548 11.6354 13.3109 11.6955 13.3109 11.7696V12.8632C13.3109 13.3492 13.8299 13.6259 14.1922 13.3329L17.7816 10.4298C18.0728 10.1942 18.0728 9.72579 17.7816 9.49024L14.1922 6.58709C13.8299 6.29409 13.3109 6.57077 13.3109 7.05684V8.1504C13.3109 8.2245 13.2548 8.28454 13.1856 8.28454H7.38322C7.07952 8.28454 6.83334 8.54813 6.83334 8.87329V11.0467Z" fill={color} />
|
||||
</svg>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export const LogoutIcon = ({ color, height = 20, width = 18 }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.4351 0H1.63891C0.733765 0 0 0.727797 0 1.62558V18.3744C0 19.2722 0.733765 20 1.63891 20H10.4351C11.3403 20 12.0741 19.2722 12.0741 18.3744V12.6013H7.38321C6.54312 12.6013 5.85964 11.9039 5.85964 11.0467V8.87329C5.85964 8.01613 6.54312 7.31875 7.38323 7.31875H12.0741V1.62558C12.0741 0.727797 11.3403 0 10.4351 0ZM6.83334 11.0467C6.83334 11.3719 7.07952 11.6354 7.38321 11.6354H13.1856C13.2548 11.6354 13.3109 11.6955 13.3109 11.7696V12.8632C13.3109 13.3492 13.8299 13.6259 14.1922 13.3329L17.7816 10.4298C18.0728 10.1942 18.0728 9.72579 17.7816 9.49024L14.1922 6.58709C13.8299 6.29409 13.3109 6.57077 13.3109 7.05684V8.1504C13.3109 8.2245 13.2548 8.28454 13.1856 8.28454H7.38322C7.07952 8.28454 6.83334 8.54813 6.83334 8.87329V11.0467Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
32
src/assets/Icons/NavAdd.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const NavAdd: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="20"
|
||||
cy="19.9999"
|
||||
r="18"
|
||||
fill={theme.palette.background.paper}
|
||||
/>
|
||||
<path
|
||||
d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
30
src/assets/Icons/NavBack.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const NavBack: React.FC<SVGProps> = ({
|
||||
color,
|
||||
opacity,
|
||||
...children
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="34"
|
||||
height="34"
|
||||
viewBox="0 0 34 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M28.3334 15.5833H11.0925L19.0117 7.6641L17 5.6666L5.66669 16.9999L17 28.3333L18.9975 26.3358L11.0925 18.4166H28.3334V15.5833Z"
|
||||
fill={setColor}
|
||||
opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
46
src/assets/Icons/NavCloseTab.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const NavCloseTab: React.FC<SVGProps> = ({
|
||||
color,
|
||||
opacity,
|
||||
...children
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="8.5" cy="8.5" r="8.5" fill={theme.palette.text.primary} />
|
||||
<circle
|
||||
cx="8.5"
|
||||
cy="8.50003"
|
||||
r="6.61111"
|
||||
fill={theme.palette.background.paper}
|
||||
/>
|
||||
<path
|
||||
d="M5.66675 5.66669L11.3334 11.3334"
|
||||
stroke={theme.palette.text.primary}
|
||||
stroke-width="2"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
<path
|
||||
d="M11.3333 5.66675L5.66658 11.3334"
|
||||
stroke={theme.palette.text.primary}
|
||||
stroke-width="2"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
30
src/assets/Icons/NavMoreMenu.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const NavMoreMenu: React.FC<SVGProps> = ({
|
||||
color,
|
||||
opacity,
|
||||
...children
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
const setOpacity = opacity ? opacity : 1;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...children}
|
||||
width="34"
|
||||
height="34"
|
||||
viewBox="0 0 34 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z"
|
||||
fill={setColor}
|
||||
fill-opacity={setOpacity}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -16,8 +16,8 @@ export const Return: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2.645 5.81803H15C15.9471 5.81803 16.8557 6.20131 17.5257 6.88278C18.195 7.56497 18.5714 8.49007 18.5714 9.45445V10.909C18.5714 11.8734 18.195 12.7985 17.5257 13.4807C16.8557 14.1622 15.9471 14.5454 15 14.5454C12.0164 14.5454 8.57143 14.5454 8.57143 14.5454C8.17714 14.5454 7.85714 14.8713 7.85714 15.2727C7.85714 15.6742 8.17714 16 8.57143 16H15C16.3264 16 17.5979 15.464 18.5357 14.5091C19.4736 13.5541 20 12.2596 20 10.909C20 10.4268 20 9.93664 20 9.45445C20 8.10461 19.4736 6.80932 18.5357 5.8544C17.5979 4.9002 16.3264 4.36347 15 4.36347H2.645L6.17929 1.27906C6.47857 1.01797 6.51286 0.55832 6.25643 0.253588C6 -0.0511433 5.54857 -0.0860541 5.24929 0.175041L0.249285 4.53874C0.0914279 4.67692 0 4.87838 0 5.09075C0 5.30312 0.0914279 5.50458 0.249285 5.64276L5.24929 10.0065C5.54857 10.2676 6 10.2326 6.25643 9.92791C6.51286 9.62318 6.47857 9.16353 6.17929 8.90244L2.645 5.81803Z"
|
||||
fill={setColor}
|
||||
fill-opacity={opacity}
|
@ -1,14 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
export const ReturnIcon= ({ color = 'white', height, width }) => {
|
||||
return (
|
||||
<svg width="20" height="13" viewBox="0 0 20 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z" fill={color}/>
|
||||
</svg>
|
||||
|
||||
|
||||
export const ReturnIcon = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height="13"
|
||||
viewBox="0 0 20 13"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z"
|
||||
fill={theme.palette.text.primary}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export const ThreadsIcon= ({ color = 'white', height = 11, width = 15 }) => {
|
||||
return (
|
||||
<svg width={width} height={height} viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z" fill={color}/>
|
||||
</svg>
|
||||
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export const ThreadsIcon = ({ color = 'white', height = 11, width = 15 }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 15 11"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TradingIcon= ({ color, height, width }) => {
|
||||
return (
|
||||
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29.6627 28.5686H5.6079C5.06735 28.5647 4.53939 28.4034 4.08736 28.104L8.41371 24.3903C8.68787 24.1495 9.01146 23.9731 9.36122 23.8737C9.71097 23.7744 10.0782 23.7546 10.4364 23.8157L18.8398 25.2345C19.5576 25.3564 20.294 25.3032 20.9873 25.0792C21.6806 24.8552 22.3109 24.467 22.8255 23.947L29.617 17.1085C29.8031 16.9068 29.9044 16.64 29.8996 16.3643C29.8948 16.0886 29.7842 15.8256 29.5912 15.6307C29.3982 15.4357 29.1378 15.324 28.8649 15.3192C28.5919 15.3143 28.3278 15.4166 28.1281 15.6046L21.3225 22.4537C21.047 22.7365 20.7084 22.9485 20.3351 23.0719C19.9618 23.1953 19.5646 23.2266 19.1769 23.1631L10.7876 21.7195C10.1245 21.6058 9.44477 21.6422 8.79738 21.8263C8.15 22.0103 7.55117 22.3373 7.04417 22.7836L2.87233 26.3624C2.82441 26.1554 2.79968 25.9437 2.79859 25.7311V24.3123L7.18463 16.8389C7.44944 16.3923 7.84654 16.041 8.3198 15.8348C8.79305 15.6286 9.31851 15.5778 9.82188 15.6897L17.5299 17.3745C18.1946 17.5208 18.8833 17.5152 19.5455 17.358C20.2078 17.2009 20.8269 16.8963 21.3576 16.4665L29.5327 9.8408C29.6456 9.75519 29.7402 9.64756 29.8111 9.52429C29.8819 9.40103 29.9275 9.26464 29.9452 9.12323C29.9629 8.98181 29.9522 8.83826 29.9139 8.70108C29.8756 8.5639 29.8104 8.4359 29.7221 8.32467C29.6339 8.21344 29.5244 8.12125 29.4002 8.05357C29.276 7.98589 29.1396 7.9441 28.9991 7.93069C28.8587 7.91727 28.7169 7.9325 28.5824 7.97547C28.4478 8.01844 28.3232 8.08828 28.2159 8.18084L20.0408 14.8065C19.755 15.0381 19.4216 15.2023 19.065 15.2869C18.7083 15.3716 18.3374 15.3747 17.9794 15.296L10.2714 13.597C9.33145 13.387 8.34986 13.4825 7.46689 13.8698C6.58392 14.2571 5.84477 14.9164 5.35506 15.7535L2.79859 20.1411V1.74669C2.79859 1.46448 2.68759 1.19383 2.49003 0.994272C2.29246 0.794718 2.0245 0.68261 1.74509 0.68261C1.46569 0.68261 1.19773 0.794718 1.00016 0.994272C0.802591 1.19383 0.691598 1.46448 0.691598 1.74669V25.7311C0.689568 26.7954 1.02832 27.8318 1.6573 28.6857C1.6573 28.707 1.68539 28.7318 1.70295 28.7531C2.1613 29.3522 2.74923 29.8376 3.42178 30.172C4.09433 30.5064 4.83369 30.6811 5.58332 30.6826H29.6381C29.9175 30.6826 30.1855 30.5705 30.383 30.3709C30.5806 30.1714 30.6916 29.9007 30.6916 29.6185C30.6916 29.3363 30.5806 29.0657 30.383 28.8661C30.1855 28.6666 29.9175 28.5544 29.6381 28.5544L29.6627 28.5686Z" fill={color}/>
|
||||
</svg>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export const TradingIcon = ({ color, height, width }) => {
|
||||
return (
|
||||
<svg
|
||||
width="31"
|
||||
height="31"
|
||||
viewBox="0 0 31 31"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M29.6627 28.5686H5.6079C5.06735 28.5647 4.53939 28.4034 4.08736 28.104L8.41371 24.3903C8.68787 24.1495 9.01146 23.9731 9.36122 23.8737C9.71097 23.7744 10.0782 23.7546 10.4364 23.8157L18.8398 25.2345C19.5576 25.3564 20.294 25.3032 20.9873 25.0792C21.6806 24.8552 22.3109 24.467 22.8255 23.947L29.617 17.1085C29.8031 16.9068 29.9044 16.64 29.8996 16.3643C29.8948 16.0886 29.7842 15.8256 29.5912 15.6307C29.3982 15.4357 29.1378 15.324 28.8649 15.3192C28.5919 15.3143 28.3278 15.4166 28.1281 15.6046L21.3225 22.4537C21.047 22.7365 20.7084 22.9485 20.3351 23.0719C19.9618 23.1953 19.5646 23.2266 19.1769 23.1631L10.7876 21.7195C10.1245 21.6058 9.44477 21.6422 8.79738 21.8263C8.15 22.0103 7.55117 22.3373 7.04417 22.7836L2.87233 26.3624C2.82441 26.1554 2.79968 25.9437 2.79859 25.7311V24.3123L7.18463 16.8389C7.44944 16.3923 7.84654 16.041 8.3198 15.8348C8.79305 15.6286 9.31851 15.5778 9.82188 15.6897L17.5299 17.3745C18.1946 17.5208 18.8833 17.5152 19.5455 17.358C20.2078 17.2009 20.8269 16.8963 21.3576 16.4665L29.5327 9.8408C29.6456 9.75519 29.7402 9.64756 29.8111 9.52429C29.8819 9.40103 29.9275 9.26464 29.9452 9.12323C29.9629 8.98181 29.9522 8.83826 29.9139 8.70108C29.8756 8.5639 29.8104 8.4359 29.7221 8.32467C29.6339 8.21344 29.5244 8.12125 29.4002 8.05357C29.276 7.98589 29.1396 7.9441 28.9991 7.93069C28.8587 7.91727 28.7169 7.9325 28.5824 7.97547C28.4478 8.01844 28.3232 8.08828 28.2159 8.18084L20.0408 14.8065C19.755 15.0381 19.4216 15.2023 19.065 15.2869C18.7083 15.3716 18.3374 15.3747 17.9794 15.296L10.2714 13.597C9.33145 13.387 8.34986 13.4825 7.46689 13.8698C6.58392 14.2571 5.84477 14.9164 5.35506 15.7535L2.79859 20.1411V1.74669C2.79859 1.46448 2.68759 1.19383 2.49003 0.994272C2.29246 0.794718 2.0245 0.68261 1.74509 0.68261C1.46569 0.68261 1.19773 0.794718 1.00016 0.994272C0.802591 1.19383 0.691598 1.46448 0.691598 1.74669V25.7311C0.689568 26.7954 1.02832 27.8318 1.6573 28.6857C1.6573 28.707 1.68539 28.7318 1.70295 28.7531C2.1613 29.3522 2.74923 29.8376 3.42178 30.172C4.09433 30.5064 4.83369 30.6811 5.58332 30.6826H29.6381C29.9175 30.6826 30.1855 30.5705 30.383 30.3709C30.5806 30.1714 30.6916 29.9007 30.6916 29.6185C30.6916 29.3363 30.5806 29.0657 30.383 28.8661C30.1855 28.6666 29.9175 28.5544 29.6381 28.5544L29.6627 28.5686Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { SVGProps } from '../svgs/interfaces';
|
||||
import { SVGProps } from './interfaces';
|
||||
|
||||
export const WalletIcon: React.FC<SVGProps> = ({
|
||||
color,
|
||||
|
Before Width: | Height: | Size: 7.7 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8047 0.393196V7.21185H16.3036L10.0003 13.5139L3.69697 7.21185H7.19584V0H12.8045L12.8047 0.393196ZM2.7047 16.8587V13.9861H0V18.6179C0 19.3774 0.622589 20 1.38213 20H18.6179C19.3774 20 20 19.3774 20 18.6179V13.9861H17.2962V17.2963L2.70461 17.2954L2.7047 16.8587Z" fill="white" fill-opacity="0.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 452 B |
@ -1,3 +0,0 @@
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.56485 0H16.3611C17.2662 0 18 0.727797 18 1.62558V18.3744C18 19.2722 17.2662 20 16.3611 20H7.56485C6.65969 20 5.92593 19.2722 5.92593 18.3744V12.6013H10.6168C11.4569 12.6013 12.1404 11.9039 12.1404 11.0467V8.87329C12.1404 8.01613 11.4569 7.31875 10.6168 7.31875H5.92593V1.62558C5.92593 0.727797 6.65969 0 7.56485 0ZM11.1667 11.0467C11.1667 11.3719 10.9205 11.6354 10.6168 11.6354H4.8144C4.74521 11.6354 4.68911 11.6955 4.68911 11.7696V12.8632C4.68911 13.3492 4.17007 13.6259 3.8078 13.3329L0.218431 10.4298C-0.0728102 10.1942 -0.0728102 9.72579 0.218431 9.49024L3.8078 6.58709C4.17005 6.29409 4.68911 6.57077 4.68911 7.05684V8.1504C4.68911 8.2245 4.74521 8.28454 4.8144 8.28454H10.6168C10.9205 8.28454 11.1667 8.54813 11.1667 8.87329V11.0467Z" fill="white" fill-opacity="0.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 932 B |
@ -1,4 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="20" cy="19.9999" r="18" fill="#434343"/>
|
||||
<path d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 602 B |
@ -1,10 +0,0 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_400_537)">
|
||||
<path d="M28.3334 15.5833H11.0925L19.0117 7.6641L17 5.6666L5.66669 16.9999L17 28.3333L18.9975 26.3358L11.0925 18.4166H28.3334V15.5833Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_400_537">
|
||||
<rect width="34" height="34" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 396 B |
@ -1,6 +0,0 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8.5" cy="8.5" r="8.5" fill="white"/>
|
||||
<circle cx="8.5" cy="8.50003" r="6.61111" fill="#434343"/>
|
||||
<path d="M5.66675 5.66669L11.3334 11.3334" stroke="white" stroke-width="2"/>
|
||||
<path d="M11.3333 5.66675L5.66658 11.3334" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 365 B |
@ -1,10 +0,0 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_400_556)">
|
||||
<path d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_400_556">
|
||||
<rect width="34" height="34" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 874 B |
@ -51,6 +51,10 @@ export const sortablePinnedAppsAtom = atom({
|
||||
name: 'Q-Search',
|
||||
service: 'APP',
|
||||
},
|
||||
{
|
||||
name: 'Q-Nodecontrol',
|
||||
service: 'APP',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
1240
src/background.ts
@ -1,15 +1,15 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const Spacer = ({ height, width, ...props }: any) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: height ? height : '0px',
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
width: width ? width : '0px',
|
||||
...(props || {})
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: height ? height : '0px',
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
width: width ? width : '0px',
|
||||
...(props || {}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,14 @@
|
||||
import { Box, Rating, Typography } from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { getFee } from "../../background";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { StarFilledIcon } from "../../assets/svgs/StarFilled";
|
||||
import { StarEmptyIcon } from "../../assets/svgs/StarEmpty";
|
||||
import { AppInfoUserName } from "./Apps-styles";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { Box, Rating } from '@mui/material';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { getFee } from '../../background';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { StarFilledIcon } from '../../assets/Icons/StarFilled';
|
||||
import { StarEmptyIcon } from '../../assets/Icons/StarEmpty';
|
||||
import { AppInfoUserName } from './Apps-styles';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
|
||||
export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
|
||||
const [value, setValue] = useState(0);
|
||||
const { show } = useContext(MyContext);
|
||||
const [hasPublishedRating, setHasPublishedRating] = useState<null | boolean>(
|
||||
@ -33,14 +27,14 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
const url = `${getBaseApiReact()}/polls/${pollName}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
if (responseData?.message?.includes("POLL_NO_EXISTS")) {
|
||||
if (responseData?.message?.includes('POLL_NO_EXISTS')) {
|
||||
setHasPublishedRating(false);
|
||||
} else if (responseData?.pollName) {
|
||||
setPollInfo(responseData);
|
||||
@ -48,9 +42,9 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
|
||||
|
||||
const responseVotes = await fetch(urlVotes, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
@ -59,15 +53,15 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
const voteCount = responseDataVotes.voteCounts;
|
||||
// Include initial value vote in the calculation
|
||||
const ratingVotes = voteCount.filter(
|
||||
(vote) => !vote.optionName.startsWith("initialValue-")
|
||||
(vote) => !vote.optionName.startsWith('initialValue-')
|
||||
);
|
||||
const initialValueVote = voteCount.find((vote) =>
|
||||
vote.optionName.startsWith("initialValue-")
|
||||
vote.optionName.startsWith('initialValue-')
|
||||
);
|
||||
if (initialValueVote) {
|
||||
// Convert "initialValue-X" to just "X" and add it to the ratingVotes array
|
||||
const initialRating = parseInt(
|
||||
initialValueVote.optionName.split("-")[1],
|
||||
initialValueVote.optionName.split('-')[1],
|
||||
10
|
||||
);
|
||||
ratingVotes.push({
|
||||
@ -92,7 +86,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
setValue(averageRating);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.message?.includes("POLL_NO_EXISTS")) {
|
||||
if (error?.message?.includes('POLL_NO_EXISTS')) {
|
||||
setHasPublishedRating(false);
|
||||
}
|
||||
}
|
||||
@ -105,45 +99,47 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
|
||||
const rateFunc = async (event, chosenValue, currentValue) => {
|
||||
try {
|
||||
const newValue = chosenValue || currentValue
|
||||
if (!myName) throw new Error("You need a name to rate.");
|
||||
const newValue = chosenValue || currentValue;
|
||||
if (!myName) throw new Error('You need a name to rate.');
|
||||
if (!app?.name) return;
|
||||
const fee = await getFee("CREATE_POLL");
|
||||
const fee = await getFee('CREATE_POLL');
|
||||
|
||||
await show({
|
||||
message: `Would you like to rate this app a rating of ${newValue}?. It will create a POLL tx.`,
|
||||
publishFee: fee.fee + " QORT",
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
if (hasPublishedRating === false) {
|
||||
const pollName = `app-library-${app.service}-rating-${app.name}`;
|
||||
const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`];
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("createPoll", {
|
||||
|
||||
pollName: pollName,
|
||||
pollDescription: `Rating for ${app.service} ${app.name}`,
|
||||
pollOptions: pollOptions,
|
||||
pollOwnerAddress: myName,
|
||||
|
||||
}, 60000)
|
||||
.then((response) => {
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
} else {
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message:
|
||||
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed qortalRequest", error);
|
||||
});
|
||||
|
||||
window
|
||||
.sendMessage(
|
||||
'createPoll',
|
||||
{
|
||||
pollName: pollName,
|
||||
pollDescription: `Rating for ${app.service} ${app.name}`,
|
||||
pollOptions: pollOptions,
|
||||
pollOwnerAddress: myName,
|
||||
},
|
||||
60000
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
} else {
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed qortalRequest', error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const pollName = `app-library-${app.service}-rating-${app.name}`;
|
||||
@ -152,39 +148,41 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
(option) => +option.optionName === +newValue
|
||||
);
|
||||
if (isNaN(optionIndex) || optionIndex === -1)
|
||||
throw new Error("Cannot find rating option");
|
||||
throw new Error('Cannot find rating option');
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("voteOnPoll", {
|
||||
|
||||
pollName: pollName,
|
||||
optionIndex,
|
||||
|
||||
}, 60000)
|
||||
.then((response) => {
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
} else {
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message:
|
||||
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed qortalRequest", error);
|
||||
});
|
||||
|
||||
window
|
||||
.sendMessage(
|
||||
'voteOnPoll',
|
||||
{
|
||||
pollName: pollName,
|
||||
optionIndex,
|
||||
},
|
||||
60000
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
} else {
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed qortalRequest', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
console.log('error', error);
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to rate",
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to rate',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
@ -194,17 +192,17 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
<div>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: ratingCountPosition === "top" ? "column" : "row",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: ratingCountPosition === 'top' ? 'column' : 'row',
|
||||
}}
|
||||
>
|
||||
{ratingCountPosition === "top" && (
|
||||
{ratingCountPosition === 'top' && (
|
||||
<>
|
||||
<AppInfoUserName>
|
||||
{(votesInfo?.totalVotes ?? 0) +
|
||||
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{" "}
|
||||
{" RATINGS"}
|
||||
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{' '}
|
||||
{' RATINGS'}
|
||||
</AppInfoUserName>
|
||||
<Spacer height="6px" />
|
||||
<AppInfoUserName>{value?.toFixed(1)}</AppInfoUserName>
|
||||
@ -214,17 +212,17 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
|
||||
<Rating
|
||||
value={value}
|
||||
onChange={(event, rating)=> rateFunc(event, rating, value)}
|
||||
onChange={(event, rating) => rateFunc(event, rating, value)}
|
||||
precision={1}
|
||||
size="small"
|
||||
icon={<StarFilledIcon />}
|
||||
emptyIcon={<StarEmptyIcon />}
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "2px",
|
||||
display: 'flex',
|
||||
gap: '2px',
|
||||
}}
|
||||
/>
|
||||
{ratingCountPosition === "right" && (
|
||||
{ratingCountPosition === 'right' && (
|
||||
<AppInfoUserName>
|
||||
{(votesInfo?.totalVotes ?? 0) +
|
||||
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}
|
||||
|
@ -18,7 +18,7 @@ export const AppsParent = styled(Box)(({ theme }) => ({
|
||||
scrollbarWidth: 'none', // Hides the scrollbar in Firefox
|
||||
|
||||
// Optional for better cross-browser consistency
|
||||
'-msOverflowStyle': 'none', // Hides scrollbar in IE and Edge
|
||||
msOverflowStyle: 'none', // Hides scrollbar in IE and Edge
|
||||
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
@ -56,61 +56,59 @@ export const AppsWidthLimiter = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '90%',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderRadius: '8px',
|
||||
padding: '0px 10px',
|
||||
height: '36px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: '8px',
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
height: '36px',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0px 10px',
|
||||
width: '90%',
|
||||
}));
|
||||
|
||||
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '90%',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start',
|
||||
width: '90%',
|
||||
}));
|
||||
|
||||
export const AppsSearchRight = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '90%',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
flexShrink: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexShrink: 1,
|
||||
justifyContent: 'flex-end',
|
||||
width: '90%',
|
||||
}));
|
||||
|
||||
export const AppCircleContainer = styled(Box)(({ theme }) => ({
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const Add = styled(Typography)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '36px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '43.57px',
|
||||
textAlign: 'left',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
|
||||
'-webkit-box-orient': 'vertical',
|
||||
'-webkit-line-clamp': '2',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: '-webkit-box',
|
||||
@ -119,15 +117,17 @@ export const AppCircleLabel = styled(Typography)(({ theme }) => ({
|
||||
lineHeight: 1.2,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: '2',
|
||||
width: '120%',
|
||||
}));
|
||||
|
||||
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const AppCircle = styled(Box)(({ theme }) => ({
|
||||
@ -312,58 +312,58 @@ export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
height: '60px',
|
||||
justifyContent: 'center',
|
||||
width: '60px',
|
||||
}));
|
||||
|
||||
export const PublishQAppInfo = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '10px',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.2,
|
||||
fontStyle: 'italic',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '10px',
|
||||
fontStyle: 'italic',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.2,
|
||||
}));
|
||||
|
||||
export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
|
||||
width: '101px',
|
||||
height: '30px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: '5px',
|
||||
fontWeight: 600,
|
||||
fontSize: '10px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: '5px',
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
fontSize: '10px',
|
||||
fontWeight: 600,
|
||||
height: '30px',
|
||||
justifyContent: 'center',
|
||||
width: '101px',
|
||||
}));
|
||||
|
||||
export const AppsCategoryInfo = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
export const AppsCategoryInfoSub = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
|
||||
|
@ -1,45 +1,47 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { AppsHome } from "./AppsHome";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import { AppInfo } from "./AppInfo";
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AppsHome } from './AppsHome';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { AppInfo } from './AppInfo';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import { AppsParent } from "./Apps-styles";
|
||||
import AppViewerContainer from "./AppViewerContainer";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { AppPublish } from "./AppPublish";
|
||||
import { AppsCategory } from "./AppsCategory";
|
||||
import { AppsLibrary } from "./AppsLibrary";
|
||||
} from '../../utils/events';
|
||||
import { AppsParent } from './Apps-styles';
|
||||
import AppViewerContainer from './AppViewerContainer';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { AppPublish } from './AppPublish';
|
||||
import { AppsCategory } from './AppsCategory';
|
||||
import { AppsLibrary } from './AppsLibrary';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
export const Apps = ({ mode, setMode, show, myName }) => {
|
||||
const [availableQapps, setAvailableQapps] = useState([]);
|
||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null)
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [selectedTab, setSelectedTab] = useState(null);
|
||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||
const [categories, setCategories] = useState([])
|
||||
const [categories, setCategories] = useState([]);
|
||||
const iframeRefs = useRef({});
|
||||
|
||||
const myApp = useMemo(() => {
|
||||
return availableQapps.find(
|
||||
(app) => app.name === myName && app.service === 'APP'
|
||||
);
|
||||
}, [myName, availableQapps]);
|
||||
|
||||
const myApp = useMemo(()=> {
|
||||
|
||||
return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
|
||||
}, [myName, availableQapps])
|
||||
const myWebsite = useMemo(()=> {
|
||||
|
||||
return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
|
||||
}, [myName, availableQapps])
|
||||
const myWebsite = useMemo(() => {
|
||||
return availableQapps.find(
|
||||
(app) => app.name === myName && app.service === 'WEBSITE'
|
||||
);
|
||||
}, [myName, availableQapps]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: tabs,
|
||||
selectedTab: selectedTab,
|
||||
@ -54,17 +56,17 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
const url = `${getBaseApiReact()}/arbitrary/categories`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!response?.ok) return;
|
||||
const responseData = await response.json();
|
||||
|
||||
|
||||
setCategories(responseData);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
}
|
||||
@ -78,9 +80,9 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!response?.ok) return;
|
||||
@ -88,75 +90,84 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
|
||||
|
||||
const responseWebsites = await fetch(urlWebsites, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!responseWebsites?.ok) return;
|
||||
const responseDataWebsites = await responseWebsites.json();
|
||||
|
||||
|
||||
apps = responseData;
|
||||
websites = responseDataWebsites;
|
||||
const combine = [...apps, ...websites];
|
||||
setAvailableQapps(combine);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
getQapps();
|
||||
getCategories()
|
||||
getCategories();
|
||||
}, [getQapps, getCategories]);
|
||||
|
||||
const selectedAppInfoFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data);
|
||||
setMode("appInfo");
|
||||
setMode('appInfo');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
subscribeToEvent('selectedAppInfo', selectedAppInfoFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
unsubscribeFromEvent('selectedAppInfo', selectedAppInfoFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectedAppInfoCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data);
|
||||
setMode("appInfo-from-category");
|
||||
setMode('appInfo-from-category');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
|
||||
subscribeToEvent('selectedAppInfoCategory', selectedAppInfoCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
|
||||
unsubscribeFromEvent(
|
||||
'selectedAppInfoCategory',
|
||||
selectedAppInfoCategoryFunc
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const selectedCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedCategory(data);
|
||||
setMode("category");
|
||||
setMode('category');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedCategory", selectedCategoryFunc);
|
||||
subscribeToEvent('selectedCategory', selectedCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedCategory", selectedCategoryFunc);
|
||||
unsubscribeFromEvent('selectedCategory', selectedCategoryFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const navigateBackFunc = (e) => {
|
||||
if (['category', 'appInfo-from-category', 'appInfo', 'library', 'publish'].includes(mode)) {
|
||||
if (
|
||||
[
|
||||
'category',
|
||||
'appInfo-from-category',
|
||||
'appInfo',
|
||||
'library',
|
||||
'publish',
|
||||
].includes(mode)
|
||||
) {
|
||||
// Handle the various modes as needed
|
||||
if (mode === 'category') {
|
||||
setMode('library');
|
||||
@ -174,16 +185,16 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
} else if (mode === 'publish') {
|
||||
setMode('library');
|
||||
}
|
||||
} else if(selectedTab?.tabId) {
|
||||
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
|
||||
} else if (selectedTab?.tabId) {
|
||||
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("navigateBack", navigateBackFunc);
|
||||
subscribeToEvent('navigateBack', navigateBackFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("navigateBack", navigateBackFunc);
|
||||
unsubscribeFromEvent('navigateBack', navigateBackFunc);
|
||||
};
|
||||
}, [mode, selectedTab]);
|
||||
|
||||
@ -195,16 +206,16 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
};
|
||||
setTabs((prev) => [...prev, newTab]);
|
||||
setSelectedTab(newTab);
|
||||
setMode("viewer");
|
||||
setMode('viewer');
|
||||
|
||||
setIsNewTabWindow(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("addTab", addTabFunc);
|
||||
subscribeToEvent('addTab', addTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("addTab", addTabFunc);
|
||||
unsubscribeFromEvent('addTab', addTabFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
@ -213,7 +224,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
|
||||
setSelectedTab(data);
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: tabs,
|
||||
selectedTab: data,
|
||||
@ -225,10 +236,10 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
|
||||
subscribeToEvent('setSelectedTab', setSelectedTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
|
||||
unsubscribeFromEvent('setSelectedTab', setSelectedTabFunc);
|
||||
};
|
||||
}, [tabs, isNewTabWindow]);
|
||||
|
||||
@ -236,14 +247,14 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
const data = e.detail?.data;
|
||||
const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId);
|
||||
if (copyTabs?.length === 0) {
|
||||
setMode("home");
|
||||
setMode('home');
|
||||
} else {
|
||||
setSelectedTab(copyTabs[0]);
|
||||
}
|
||||
setTabs(copyTabs);
|
||||
setSelectedTab(copyTabs[0]);
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: copyTabs,
|
||||
selectedTab: copyTabs[0],
|
||||
@ -253,59 +264,74 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("removeTab", removeTabFunc);
|
||||
subscribeToEvent('removeTab', removeTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("removeTab", removeTabFunc);
|
||||
unsubscribeFromEvent('removeTab', removeTabFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
const setNewTabWindowFunc = (e) => {
|
||||
setIsNewTabWindow(true);
|
||||
setSelectedTab(null)
|
||||
setSelectedTab(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
|
||||
subscribeToEvent('newTabWindow', setNewTabWindowFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
|
||||
unsubscribeFromEvent('newTabWindow', setNewTabWindowFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
|
||||
return (
|
||||
<AppsParent
|
||||
sx={{
|
||||
display: !show && "none",
|
||||
display: !show && 'none',
|
||||
}}
|
||||
>
|
||||
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
|
||||
{mode === "home" && (
|
||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
)}
|
||||
|
||||
<AppsLibrary
|
||||
isShow={mode === "library" && !selectedTab}
|
||||
{mode !== 'viewer' && !selectedTab && <Spacer height="30px" />}
|
||||
{mode === 'home' && (
|
||||
<AppsHome
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
categories={categories}
|
||||
myApp={myApp}
|
||||
myWebsite={myWebsite}
|
||||
/>
|
||||
|
||||
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
|
||||
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
|
||||
)}
|
||||
|
||||
<AppsLibrary
|
||||
isShow={mode === 'library' && !selectedTab}
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
categories={categories}
|
||||
/>
|
||||
|
||||
{mode === 'appInfo' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
{mode === 'appInfo-from-category' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
<AppsCategory
|
||||
availableQapps={availableQapps}
|
||||
isShow={mode === 'category' && !selectedTab}
|
||||
category={selectedCategory}
|
||||
myName={myName}
|
||||
/>
|
||||
{mode === 'publish' && !selectedTab && (
|
||||
<AppPublish names={myName ? [myName] : []} categories={categories} />
|
||||
)}
|
||||
|
||||
{tabs.map((tab) => {
|
||||
if (!iframeRefs.current[tab.tabId]) {
|
||||
iframeRefs.current[tab.tabId] = React.createRef();
|
||||
}
|
||||
if (!iframeRefs.current[tab.tabId]) {
|
||||
iframeRefs.current[tab.tabId] = React.createRef();
|
||||
}
|
||||
return (
|
||||
<AppViewerContainer
|
||||
key={tab?.tabId}
|
||||
key={tab?.tabId}
|
||||
hide={isNewTabWindow}
|
||||
isSelected={tab?.tabId === selectedTab?.tabId}
|
||||
app={tab}
|
||||
@ -314,13 +340,18 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
);
|
||||
})}
|
||||
|
||||
{isNewTabWindow && mode === "viewer" && (
|
||||
{isNewTabWindow && mode === 'viewer' && (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHome
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myApp={myApp}
|
||||
myWebsite={myWebsite}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
||||
{mode !== 'viewer' && !selectedTab && <Spacer height="180px" />}
|
||||
</AppsParent>
|
||||
);
|
||||
};
|
||||
|
@ -42,7 +42,8 @@ const officialAppList = [
|
||||
"q-support",
|
||||
"q-manager",
|
||||
"q-wallets",
|
||||
"q-search"
|
||||
"q-search",
|
||||
"q-nodecontrol"
|
||||
];
|
||||
|
||||
const ScrollerStyled = styled('div')({
|
||||
|
@ -5,7 +5,7 @@ import React, {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
} from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -23,67 +23,71 @@ import {
|
||||
PublishQAppCTAParent,
|
||||
PublishQAppCTARight,
|
||||
PublishQAppDotsBG,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import IconSearch from "../../assets/svgs/Search.svg";
|
||||
import IconClearInput from "../../assets/svgs/ClearInput.svg";
|
||||
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
|
||||
import qappDots from "../../assets/svgs/qappDots.svg";
|
||||
} from './Apps-styles';
|
||||
import { Avatar, Box, ButtonBase, InputBase, styled } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import IconSearch from '../../assets/svgs/Search.svg';
|
||||
import IconClearInput from '../../assets/svgs/ClearInput.svg';
|
||||
import qappDevelopText from '../../assets/svgs/qappDevelopText.svg';
|
||||
import qappDots from '../../assets/svgs/qappDots.svg';
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { AppInfoSnippet } from "./AppInfoSnippet";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { AppsDesktopLibraryBody, AppsDesktopLibraryHeader } from "./AppsDesktop-styles";
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { AppInfoSnippet } from './AppInfoSnippet';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import {
|
||||
AppsDesktopLibraryBody,
|
||||
AppsDesktopLibraryHeader,
|
||||
} from './AppsDesktop-styles';
|
||||
const officialAppList = [
|
||||
"q-tube",
|
||||
"q-blog",
|
||||
"q-share",
|
||||
"q-support",
|
||||
"q-mail",
|
||||
"q-fund",
|
||||
"q-shop",
|
||||
"q-trade",
|
||||
"q-support",
|
||||
"q-manager",
|
||||
"q-wallets",
|
||||
"q-search"
|
||||
'q-tube',
|
||||
'q-blog',
|
||||
'q-share',
|
||||
'q-support',
|
||||
'q-mail',
|
||||
'q-fund',
|
||||
'q-shop',
|
||||
'q-trade',
|
||||
'q-support',
|
||||
'q-manager',
|
||||
'q-wallets',
|
||||
'q-search',
|
||||
'q-nodecontrol',
|
||||
];
|
||||
|
||||
const ScrollerStyled = styled("div")({
|
||||
const ScrollerStyled = styled('div')({
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
'::-webkit-scrollbar': {
|
||||
width: '0px',
|
||||
height: '0px',
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
scrollbarWidth: 'none',
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-msOverflowStyle": "none",
|
||||
'-msOverflowStyle': 'none',
|
||||
});
|
||||
|
||||
const StyledVirtuosoContainer = styled("div")({
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
const StyledVirtuosoContainer = styled('div')({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
'::-webkit-scrollbar': {
|
||||
width: '0px',
|
||||
height: '0px',
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
scrollbarWidth: 'none',
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-msOverflowStyle": "none",
|
||||
'-msOverflowStyle': 'none',
|
||||
});
|
||||
|
||||
export const AppsCategoryDesktop = ({
|
||||
@ -92,29 +96,28 @@ export const AppsCategoryDesktop = ({
|
||||
category,
|
||||
isShow,
|
||||
}) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const virtuosoRef = useRef();
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
|
||||
const categoryList = useMemo(() => {
|
||||
if(category?.id === 'all') return availableQapps
|
||||
if (category?.id === 'all') return availableQapps;
|
||||
return availableQapps.filter(
|
||||
(app) => app?.metadata?.category === category?.id
|
||||
);
|
||||
}, [availableQapps, category]);
|
||||
|
||||
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
||||
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
|
||||
|
||||
// Debounce logic
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(searchValue);
|
||||
|
||||
}, 350);
|
||||
setTimeout(() => {
|
||||
virtuosoRef.current.scrollToIndex({
|
||||
index: 0
|
||||
});
|
||||
if (virtuosoRef.current) {
|
||||
virtuosoRef.current.scrollToIndex({ index: 0 });
|
||||
}
|
||||
}, 500);
|
||||
// Cleanup timeout if searchValue changes before the timeout completes
|
||||
return () => {
|
||||
@ -126,8 +129,13 @@ export const AppsCategoryDesktop = ({
|
||||
|
||||
const searchedList = useMemo(() => {
|
||||
if (!debouncedValue) return categoryList;
|
||||
return categoryList.filter((app) =>
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
|
||||
return categoryList.filter(
|
||||
(app) =>
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) ||
|
||||
(app?.metadata?.title &&
|
||||
app?.metadata?.title
|
||||
?.toLowerCase()
|
||||
.includes(debouncedValue.toLowerCase()))
|
||||
);
|
||||
}, [debouncedValue, categoryList]);
|
||||
|
||||
@ -140,7 +148,7 @@ export const AppsCategoryDesktop = ({
|
||||
myName={myName}
|
||||
isFromCategory={true}
|
||||
parentStyles={{
|
||||
padding: '0px 10px'
|
||||
padding: '0px 10px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -149,27 +157,29 @@ export const AppsCategoryDesktop = ({
|
||||
return (
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
display: !isShow && "none",
|
||||
padding: "0px",
|
||||
height: "100vh",
|
||||
overflow: "hidden",
|
||||
paddingTop: "30px",
|
||||
display: !isShow && 'none',
|
||||
padding: '0px',
|
||||
height: '100vh',
|
||||
overflow: 'hidden',
|
||||
paddingTop: '30px',
|
||||
}}
|
||||
>
|
||||
<AppsDesktopLibraryHeader
|
||||
sx={{
|
||||
maxWidth: "1500px",
|
||||
width: "90%",
|
||||
maxWidth: '1500px',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<AppsWidthLimiter
|
||||
sx={{
|
||||
alignItems: "flex-end",
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer sx={{
|
||||
width: "412px",
|
||||
}}>
|
||||
<AppsSearchContainer
|
||||
sx={{
|
||||
width: '412px',
|
||||
}}
|
||||
>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<InputBase
|
||||
@ -178,8 +188,8 @@ export const AppsCategoryDesktop = ({
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{
|
||||
"aria-label": "Search for apps",
|
||||
fontSize: "16px",
|
||||
'aria-label': 'Search for apps',
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
/>
|
||||
@ -188,7 +198,7 @@ export const AppsCategoryDesktop = ({
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSearchValue("");
|
||||
setSearchValue('');
|
||||
}}
|
||||
>
|
||||
<img src={IconClearInput} />
|
||||
@ -201,9 +211,9 @@ export const AppsCategoryDesktop = ({
|
||||
<AppsDesktopLibraryBody
|
||||
sx={{
|
||||
height: `calc(100vh - 36px)`,
|
||||
overflow: "auto",
|
||||
padding: "0px",
|
||||
alignItems: "center",
|
||||
overflow: 'auto',
|
||||
padding: '0px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Spacer height="25px" />
|
||||
@ -214,7 +224,7 @@ export const AppsCategoryDesktop = ({
|
||||
</AppsWidthLimiter>
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer
|
||||
sx={{
|
||||
sx={{
|
||||
height: `calc(100vh - 36px - 90px - 25px)`,
|
||||
}}
|
||||
>
|
||||
|
@ -1,34 +1,29 @@
|
||||
import React, {
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { AppsHomeDesktop } from "./AppsHomeDesktop";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { GlobalContext, getBaseApiReact } from "../../App";
|
||||
import { AppInfo } from "./AppInfo";
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AppsHomeDesktop } from './AppsHomeDesktop';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { GlobalContext, getBaseApiReact } from '../../App';
|
||||
import { AppInfo } from './AppInfo';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import { AppsParent } from "./Apps-styles";
|
||||
import AppViewerContainer from "./AppViewerContainer";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { AppPublish } from "./AppPublish";
|
||||
import { AppsLibraryDesktop } from "./AppsLibraryDesktop";
|
||||
import { AppsCategoryDesktop } from "./AppsCategoryDesktop";
|
||||
import { AppsNavBarDesktop } from "./AppsNavBarDesktop";
|
||||
import { Box, ButtonBase, useTheme } from "@mui/material";
|
||||
import { HomeIcon } from "../../assets/Icons/HomeIcon";
|
||||
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
|
||||
import { Save } from "../Save/Save";
|
||||
import { IconWrapper } from "../Desktop/DesktopFooter";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { enabledDevModeAtom } from "../../atoms/global";
|
||||
import { AppsIcon } from "../../assets/Icons/AppsIcon";
|
||||
} from '../../utils/events';
|
||||
import { AppsParent } from './Apps-styles';
|
||||
import AppViewerContainer from './AppViewerContainer';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { AppPublish } from './AppPublish';
|
||||
import { AppsLibraryDesktop } from './AppsLibraryDesktop';
|
||||
import { AppsCategoryDesktop } from './AppsCategoryDesktop';
|
||||
import { AppsNavBarDesktop } from './AppsNavBarDesktop';
|
||||
import { Box, ButtonBase, useTheme } from '@mui/material';
|
||||
import { HomeIcon } from '../../assets/Icons/HomeIcon';
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
import { Save } from '../Save/Save';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { enabledDevModeAtom } from '../../atoms/global';
|
||||
import { AppsIcon } from '../../assets/Icons/AppsIcon';
|
||||
import { CoreSyncStatus } from '../CoreSyncStatus';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
@ -58,25 +53,25 @@ export const AppsDesktop = ({
|
||||
|
||||
const myApp = useMemo(() => {
|
||||
return availableQapps.find(
|
||||
(app) => app.name === myName && app.service === "APP"
|
||||
(app) => app.name === myName && app.service === 'APP'
|
||||
);
|
||||
}, [myName, availableQapps]);
|
||||
|
||||
const myWebsite = useMemo(() => {
|
||||
return availableQapps.find(
|
||||
(app) => app.name === myName && app.service === "WEBSITE"
|
||||
(app) => app.name === myName && app.service === 'WEBSITE'
|
||||
);
|
||||
}, [myName, availableQapps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
showTutorial("qapps");
|
||||
showTutorial('qapps');
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: tabs,
|
||||
selectedTab: selectedTab,
|
||||
@ -91,9 +86,9 @@ export const AppsDesktop = ({
|
||||
const url = `${getBaseApiReact()}/arbitrary/categories`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!response?.ok) return;
|
||||
@ -115,9 +110,9 @@ export const AppsDesktop = ({
|
||||
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!response?.ok) return;
|
||||
@ -125,9 +120,9 @@ export const AppsDesktop = ({
|
||||
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
|
||||
|
||||
const responseWebsites = await fetch(urlWebsites, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!responseWebsites?.ok) return;
|
||||
@ -150,9 +145,12 @@ export const AppsDesktop = ({
|
||||
useEffect(() => {
|
||||
getQapps();
|
||||
|
||||
const interval = setInterval(() => {
|
||||
getQapps();
|
||||
}, 20 * 60 * 1000); // 20 minutes in milliseconds
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
getQapps();
|
||||
},
|
||||
20 * 60 * 1000
|
||||
); // 20 minutes in milliseconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [getQapps]);
|
||||
@ -160,29 +158,29 @@ export const AppsDesktop = ({
|
||||
const selectedAppInfoFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data);
|
||||
setMode("appInfo");
|
||||
setMode('appInfo');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
subscribeToEvent('selectedAppInfo', selectedAppInfoFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
unsubscribeFromEvent('selectedAppInfo', selectedAppInfoFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectedAppInfoCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data);
|
||||
setMode("appInfo-from-category");
|
||||
setMode('appInfo-from-category');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
|
||||
subscribeToEvent('selectedAppInfoCategory', selectedAppInfoCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent(
|
||||
"selectedAppInfoCategory",
|
||||
'selectedAppInfoCategory',
|
||||
selectedAppInfoCategoryFunc
|
||||
);
|
||||
};
|
||||
@ -191,43 +189,43 @@ export const AppsDesktop = ({
|
||||
const selectedCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedCategory(data);
|
||||
setMode("category");
|
||||
setMode('category');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedCategory", selectedCategoryFunc);
|
||||
subscribeToEvent('selectedCategory', selectedCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedCategory", selectedCategoryFunc);
|
||||
unsubscribeFromEvent('selectedCategory', selectedCategoryFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigateBackFunc = (e) => {
|
||||
if (
|
||||
[
|
||||
"category",
|
||||
"appInfo-from-category",
|
||||
"appInfo",
|
||||
"library",
|
||||
"publish",
|
||||
'category',
|
||||
'appInfo-from-category',
|
||||
'appInfo',
|
||||
'library',
|
||||
'publish',
|
||||
].includes(mode)
|
||||
) {
|
||||
// Handle the various modes as needed
|
||||
if (mode === "category") {
|
||||
setMode("library");
|
||||
if (mode === 'category') {
|
||||
setMode('library');
|
||||
setSelectedCategory(null);
|
||||
} else if (mode === "appInfo-from-category") {
|
||||
setMode("category");
|
||||
} else if (mode === "appInfo") {
|
||||
setMode("library");
|
||||
} else if (mode === "library") {
|
||||
} else if (mode === 'appInfo-from-category') {
|
||||
setMode('category');
|
||||
} else if (mode === 'appInfo') {
|
||||
setMode('library');
|
||||
} else if (mode === 'library') {
|
||||
if (isNewTabWindow) {
|
||||
setMode("viewer");
|
||||
setMode('viewer');
|
||||
} else {
|
||||
setMode("home");
|
||||
setMode('home');
|
||||
}
|
||||
} else if (mode === "publish") {
|
||||
setMode("library");
|
||||
} else if (mode === 'publish') {
|
||||
setMode('library');
|
||||
}
|
||||
} else if (selectedTab?.tabId) {
|
||||
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {});
|
||||
@ -235,10 +233,10 @@ export const AppsDesktop = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("navigateBack", navigateBackFunc);
|
||||
subscribeToEvent('navigateBack', navigateBackFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("navigateBack", navigateBackFunc);
|
||||
unsubscribeFromEvent('navigateBack', navigateBackFunc);
|
||||
};
|
||||
}, [mode, selectedTab]);
|
||||
|
||||
@ -250,16 +248,16 @@ export const AppsDesktop = ({
|
||||
};
|
||||
setTabs((prev) => [...prev, newTab]);
|
||||
setSelectedTab(newTab);
|
||||
setMode("viewer");
|
||||
setMode('viewer');
|
||||
|
||||
setIsNewTabWindow(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("addTab", addTabFunc);
|
||||
subscribeToEvent('addTab', addTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("addTab", addTabFunc);
|
||||
unsubscribeFromEvent('addTab', addTabFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
const setSelectedTabFunc = (e) => {
|
||||
@ -268,7 +266,7 @@ export const AppsDesktop = ({
|
||||
|
||||
setSelectedTab(data);
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: tabs,
|
||||
selectedTab: data,
|
||||
@ -280,10 +278,10 @@ export const AppsDesktop = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
|
||||
subscribeToEvent('setSelectedTab', setSelectedTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
|
||||
unsubscribeFromEvent('setSelectedTab', setSelectedTabFunc);
|
||||
};
|
||||
}, [tabs, isNewTabWindow]);
|
||||
|
||||
@ -291,14 +289,14 @@ export const AppsDesktop = ({
|
||||
const data = e.detail?.data;
|
||||
const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId);
|
||||
if (copyTabs?.length === 0) {
|
||||
setMode("home");
|
||||
setMode('home');
|
||||
} else {
|
||||
setSelectedTab(copyTabs[0]);
|
||||
}
|
||||
setTabs(copyTabs);
|
||||
setSelectedTab(copyTabs[0]);
|
||||
setTimeout(() => {
|
||||
executeEvent("setTabsToNav", {
|
||||
executeEvent('setTabsToNav', {
|
||||
data: {
|
||||
tabs: copyTabs,
|
||||
selectedTab: copyTabs[0],
|
||||
@ -308,10 +306,10 @@ export const AppsDesktop = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("removeTab", removeTabFunc);
|
||||
subscribeToEvent('removeTab', removeTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("removeTab", removeTabFunc);
|
||||
unsubscribeFromEvent('removeTab', removeTabFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
@ -321,10 +319,10 @@ export const AppsDesktop = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
|
||||
subscribeToEvent('newTabWindow', setNewTabWindowFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
|
||||
unsubscribeFromEvent('newTabWindow', setNewTabWindowFunc);
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
@ -333,24 +331,33 @@ export const AppsDesktop = ({
|
||||
sx={{
|
||||
position: !show && 'fixed',
|
||||
left: !show && '-200vw',
|
||||
flexDirection: 'row'
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
height: '100vh',
|
||||
width: '60px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "60px",
|
||||
paddingTop: "23px",
|
||||
width: '70px',
|
||||
height: '70px',
|
||||
paddingTop: '23px',
|
||||
}}
|
||||
>
|
||||
<CoreSyncStatus />
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
}}
|
||||
onClick={() => {
|
||||
goToHome();
|
||||
@ -361,7 +368,7 @@ export const AppsDesktop = ({
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopViewMode("apps");
|
||||
setDesktopViewMode('apps');
|
||||
}}
|
||||
>
|
||||
<IconWrapper label="Apps" disableWidth>
|
||||
@ -371,13 +378,13 @@ export const AppsDesktop = ({
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopViewMode("chat");
|
||||
setDesktopViewMode('chat');
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
color={
|
||||
hasUnreadDirects || hasUnreadGroups
|
||||
? "var(--unread)"
|
||||
? 'var(--unread)'
|
||||
: theme.palette.text.primary
|
||||
}
|
||||
label="Chat"
|
||||
@ -387,7 +394,7 @@ export const AppsDesktop = ({
|
||||
height={30}
|
||||
color={
|
||||
hasUnreadDirects || hasUnreadGroups
|
||||
? "var(--unread)"
|
||||
? 'var(--unread)'
|
||||
: theme.palette.text.primary
|
||||
}
|
||||
/>
|
||||
@ -434,7 +441,7 @@ export const AppsDesktop = ({
|
||||
{isEnabledDevMode && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopViewMode("dev");
|
||||
setDesktopViewMode('dev');
|
||||
}}
|
||||
>
|
||||
<IconWrapper label="Dev" disableWidth>
|
||||
@ -442,21 +449,21 @@ export const AppsDesktop = ({
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
)}
|
||||
{mode !== "home" && (
|
||||
{mode !== 'home' && (
|
||||
<AppsNavBarDesktop
|
||||
disableBack={isNewTabWindow && mode === "viewer"}
|
||||
disableBack={isNewTabWindow && mode === 'viewer'}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{mode === "home" && (
|
||||
{mode === 'home' && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
overflow: "auto",
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Spacer height="30px" />
|
||||
@ -471,7 +478,7 @@ export const AppsDesktop = ({
|
||||
)}
|
||||
|
||||
<AppsLibraryDesktop
|
||||
isShow={mode === "library" && !selectedTab}
|
||||
isShow={mode === 'library' && !selectedTab}
|
||||
availableQapps={availableQapps}
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
@ -480,19 +487,19 @@ export const AppsDesktop = ({
|
||||
getQapps={getQapps}
|
||||
/>
|
||||
|
||||
{mode === "appInfo" && !selectedTab && (
|
||||
{mode === 'appInfo' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
{mode === "appInfo-from-category" && !selectedTab && (
|
||||
{mode === 'appInfo-from-category' && !selectedTab && (
|
||||
<AppInfo app={selectedAppInfo} myName={myName} />
|
||||
)}
|
||||
<AppsCategoryDesktop
|
||||
availableQapps={availableQapps}
|
||||
isShow={mode === "category" && !selectedTab}
|
||||
isShow={mode === 'category' && !selectedTab}
|
||||
category={selectedCategory}
|
||||
myName={myName}
|
||||
/>
|
||||
{mode === "publish" && !selectedTab && (
|
||||
{mode === 'publish' && !selectedTab && (
|
||||
<AppPublish names={myName ? [myName] : []} categories={categories} />
|
||||
)}
|
||||
{tabs.map((tab) => {
|
||||
@ -511,15 +518,15 @@ export const AppsDesktop = ({
|
||||
);
|
||||
})}
|
||||
|
||||
{isNewTabWindow && mode === "viewer" && (
|
||||
{isNewTabWindow && mode === 'viewer' && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
overflow: "auto",
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Spacer height="30px" />
|
||||
|
@ -31,6 +31,7 @@ import { HubsIcon } from '../../assets/Icons/HubsIcon';
|
||||
import { AppsDevModeNavBar } from './AppsDevModeNavBar';
|
||||
import { AppsIcon } from '../../assets/Icons/AppsIcon';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { CoreSyncStatus } from '../CoreSyncStatus';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
@ -243,11 +244,20 @@ export const AppsDevMode = ({
|
||||
gap: '25px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '70px',
|
||||
height: '70px',
|
||||
paddingTop: '23px',
|
||||
}}
|
||||
>
|
||||
<CoreSyncStatus />
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
paddingTop: '23px',
|
||||
}}
|
||||
onClick={() => {
|
||||
goToHome();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -6,8 +6,8 @@ import {
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsParent,
|
||||
} from "./Apps-styles";
|
||||
import { Buffer } from "buffer";
|
||||
} from './Apps-styles';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
@ -20,17 +20,17 @@ import {
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Input,
|
||||
} from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { useModal } from "../../common/useModal";
|
||||
import { createEndpoint, isUsingLocal } from "../../background";
|
||||
import { Label } from "../Group/AddGroup";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import swaggerSVG from '../../assets/svgs/swagger.svg'
|
||||
} from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { MyContext, getBaseApiReact, isMobile } from '../../App';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { useModal } from '../../common/useModal';
|
||||
import { createEndpoint, isUsingLocal } from '../../background';
|
||||
import { Label } from '../Group/AddGroup';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import swaggerSVG from '../../assets/svgs/swagger.svg';
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const AppsDevModeHome = ({
|
||||
@ -40,8 +40,8 @@ export const AppsDevModeHome = ({
|
||||
availableQapps,
|
||||
myName,
|
||||
}) => {
|
||||
const [domain, setDomain] = useState("127.0.0.1");
|
||||
const [port, setPort] = useState("");
|
||||
const [domain, setDomain] = useState('127.0.0.1');
|
||||
const [port, setPort] = useState('');
|
||||
const [selectedPreviewFile, setSelectedPreviewFile] = useState(null);
|
||||
|
||||
const { isShow, onCancel, onOk, show, message } = useModal();
|
||||
@ -58,7 +58,7 @@ export const AppsDevModeHome = ({
|
||||
const content = await window.electron.readFile(filePath);
|
||||
return { buffer: content, filePath };
|
||||
} else {
|
||||
console.log("No file selected.");
|
||||
console.log('No file selected.');
|
||||
}
|
||||
};
|
||||
const handleSelectDirectry = async (existingDirectoryPath) => {
|
||||
@ -67,7 +67,7 @@ export const AppsDevModeHome = ({
|
||||
if (buffer) {
|
||||
return { buffer, directoryPath };
|
||||
} else {
|
||||
console.log("No file selected.");
|
||||
console.log('No file selected.');
|
||||
}
|
||||
};
|
||||
|
||||
@ -78,34 +78,36 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message:
|
||||
"Please use your local node for dev mode! Logout and use Local node.",
|
||||
'Please use your local node for dev mode! Logout and use Local node.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { portVal, domainVal } = await show({
|
||||
message: "",
|
||||
publishFee: "",
|
||||
message: '',
|
||||
publishFee: '',
|
||||
});
|
||||
const framework = domainVal + ":" + portVal;
|
||||
const framework = domainVal + ':' + portVal;
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/developer/proxy/start`,
|
||||
{
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: framework,
|
||||
}
|
||||
);
|
||||
const responseData = await response.text();
|
||||
executeEvent("appsDevModeAddTab", {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:" + responseData,
|
||||
url: 'http://127.0.0.1:' + responseData,
|
||||
},
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const addPreviewApp = async (isRefresh, existingFilePath, tabId) => {
|
||||
@ -115,9 +117,9 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message:
|
||||
"Please use your local node for dev mode! Logout and use Local node.",
|
||||
'Please use your local node for dev mode! Logout and use Local node.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -125,8 +127,8 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: "You need a name to use preview",
|
||||
type: 'error',
|
||||
message: 'You need a name to use preview',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -137,29 +139,29 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: "Please select a file",
|
||||
type: 'error',
|
||||
message: 'Please select a file',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const postBody = Buffer.from(buffer).toString("base64");
|
||||
const postBody = Buffer.from(buffer).toString('base64');
|
||||
|
||||
const endpoint = await createEndpoint(
|
||||
`/arbitrary/APP/${myName}/zip?preview=true`
|
||||
);
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: postBody,
|
||||
});
|
||||
if (!response?.ok) throw new Error("Invalid zip");
|
||||
if (!response?.ok) throw new Error('Invalid zip');
|
||||
const previewPath = await response.text();
|
||||
if (tabId) {
|
||||
executeEvent("appsDevModeUpdateTab", {
|
||||
executeEvent('appsDevModeUpdateTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:12391" + previewPath,
|
||||
url: 'http://127.0.0.1:12391' + previewPath,
|
||||
isPreview: true,
|
||||
filePath,
|
||||
refreshFunc: (tabId) => {
|
||||
@ -170,9 +172,9 @@ export const AppsDevModeHome = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent("appsDevModeAddTab", {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:12391" + previewPath,
|
||||
url: 'http://127.0.0.1:12391' + previewPath,
|
||||
isPreview: true,
|
||||
filePath,
|
||||
refreshFunc: (tabId) => {
|
||||
@ -192,9 +194,9 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message:
|
||||
"Please use your local node for dev mode! Logout and use Local node.",
|
||||
'Please use your local node for dev mode! Logout and use Local node.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -202,8 +204,8 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: "You need a name to use preview",
|
||||
type: 'error',
|
||||
message: 'You need a name to use preview',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -214,29 +216,29 @@ export const AppsDevModeHome = ({
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: "Please select a file",
|
||||
type: 'error',
|
||||
message: 'Please select a file',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const postBody = Buffer.from(buffer).toString("base64");
|
||||
const postBody = Buffer.from(buffer).toString('base64');
|
||||
|
||||
const endpoint = await createEndpoint(
|
||||
`/arbitrary/APP/${myName}/zip?preview=true`
|
||||
);
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: postBody,
|
||||
});
|
||||
if (!response?.ok) throw new Error("Invalid zip");
|
||||
if (!response?.ok) throw new Error('Invalid zip');
|
||||
const previewPath = await response.text();
|
||||
if (tabId) {
|
||||
executeEvent("appsDevModeUpdateTab", {
|
||||
executeEvent('appsDevModeUpdateTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:12391" + previewPath,
|
||||
url: 'http://127.0.0.1:12391' + previewPath,
|
||||
isPreview: true,
|
||||
directoryPath,
|
||||
refreshFunc: (tabId) => {
|
||||
@ -247,9 +249,9 @@ export const AppsDevModeHome = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent("appsDevModeAddTab", {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:12391" + previewPath,
|
||||
url: 'http://127.0.0.1:12391' + previewPath,
|
||||
isPreview: true,
|
||||
directoryPath,
|
||||
refreshFunc: (tabId) => {
|
||||
@ -266,12 +268,12 @@ export const AppsDevModeHome = ({
|
||||
<>
|
||||
<AppsContainer
|
||||
sx={{
|
||||
justifyContent: "flex-start",
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<AppLibrarySubTitle
|
||||
sx={{
|
||||
fontSize: "30px",
|
||||
fontSize: '30px',
|
||||
}}
|
||||
>
|
||||
Dev Mode Apps
|
||||
@ -280,8 +282,8 @@ export const AppsDevModeHome = ({
|
||||
<Spacer height="45px" />
|
||||
<AppsContainer
|
||||
sx={{
|
||||
gap: "75px",
|
||||
justifyContent: "flex-start",
|
||||
gap: '75px',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -291,7 +293,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -307,7 +309,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -323,7 +325,7 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
@ -334,10 +336,10 @@ export const AppsDevModeHome = ({
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent("appsDevModeAddTab", {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
data: {
|
||||
service: "APP",
|
||||
name: "Q-Sandbox",
|
||||
service: 'APP',
|
||||
name: 'Q-Sandbox',
|
||||
tabId: uid.rnd(),
|
||||
},
|
||||
});
|
||||
@ -345,16 +347,16 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
"& img": {
|
||||
objectFit: "fill",
|
||||
height: '42px',
|
||||
width: '42px',
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
},
|
||||
}}
|
||||
alt="Q-Sandbox"
|
||||
@ -362,8 +364,8 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
width: '31px',
|
||||
height: 'auto',
|
||||
}}
|
||||
alt="center-icon"
|
||||
/>
|
||||
@ -374,27 +376,27 @@ export const AppsDevModeHome = ({
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent("appsDevModeAddTab", {
|
||||
executeEvent('appsDevModeAddTab', {
|
||||
data: {
|
||||
url: "http://127.0.0.1:12391",
|
||||
url: 'http://127.0.0.1:12391',
|
||||
isPreview: false,
|
||||
customIcon: swaggerSVG
|
||||
customIcon: swaggerSVG,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
"& img": {
|
||||
objectFit: "fill",
|
||||
height: '42px',
|
||||
width: '42px',
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
},
|
||||
}}
|
||||
alt="API"
|
||||
@ -402,8 +404,8 @@ export const AppsDevModeHome = ({
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
width: '31px',
|
||||
height: 'auto',
|
||||
}}
|
||||
alt="center-icon"
|
||||
/>
|
||||
@ -419,20 +421,20 @@ export const AppsDevModeHome = ({
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && domain && port) {
|
||||
if (e.key === 'Enter' && domain && port) {
|
||||
onOk({ portVal: port, domainVal: domain });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Add custom framework"}
|
||||
{'Add custom framework'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Domain</Label>
|
||||
@ -444,10 +446,10 @@ export const AppsDevModeHome = ({
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
marginTop: "15px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
marginTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Label>Port</Label>
|
||||
|
@ -1,51 +1,34 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppsNavBarLeft,
|
||||
AppsNavBarParent,
|
||||
AppsNavBarRight,
|
||||
} from "./Apps-styles";
|
||||
import NavBack from "../../assets/svgs/NavBack.svg";
|
||||
import NavAdd from "../../assets/svgs/NavAdd.svg";
|
||||
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
|
||||
import {
|
||||
ButtonBase,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Tab,
|
||||
Tabs,
|
||||
} from "@mui/material";
|
||||
} from './Apps-styles';
|
||||
import { NavBack } from '../../assets/Icons/NavBack.tsx';
|
||||
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
|
||||
import { ButtonBase, Tab, Tabs } from '@mui/material';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import TabComponent from "./TabComponent";
|
||||
import PushPinIcon from "@mui/icons-material/PushPin";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
import {
|
||||
navigationControllerAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from "../../atoms/global";
|
||||
import { AppsDevModeTabComponent } from "./AppsDevModeTabComponent";
|
||||
|
||||
|
||||
} from '../../utils/events';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { navigationControllerAtom } from '../../atoms/global';
|
||||
import { AppsDevModeTabComponent } from './AppsDevModeTabComponent';
|
||||
|
||||
export const AppsDevModeNavBar = () => {
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [selectedTab, setSelectedTab] = useState(null);
|
||||
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
|
||||
const [navigationController, setNavigationController] = useRecoilState(
|
||||
navigationControllerAtom
|
||||
);
|
||||
|
||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||
const tabsRef = useRef(null);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
@ -57,27 +40,25 @@ export const AppsDevModeNavBar = () => {
|
||||
useEffect(() => {
|
||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||
if (tabsRef.current) {
|
||||
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
|
||||
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
|
||||
if (tabElements.length > 0) {
|
||||
const lastTab = tabElements[tabElements.length - 1];
|
||||
lastTab.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "end",
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [tabs.length]); // Dependency on the number of tabs
|
||||
|
||||
|
||||
const isDisableBackButton = useMemo(()=> {
|
||||
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
|
||||
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
|
||||
return false
|
||||
}, [navigationController, selectedTab])
|
||||
|
||||
|
||||
|
||||
const isDisableBackButton = useMemo(() => {
|
||||
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return false;
|
||||
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return true;
|
||||
return false;
|
||||
}, [navigationController, selectedTab]);
|
||||
|
||||
const setTabsToNav = (e) => {
|
||||
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
|
||||
@ -88,45 +69,43 @@ export const AppsDevModeNavBar = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("appsDevModeSetTabsToNav", setTabsToNav);
|
||||
subscribeToEvent('appsDevModeSetTabsToNav', setTabsToNav);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("appsDevModeSetTabsToNav", setTabsToNav);
|
||||
unsubscribeFromEvent('appsDevModeSetTabsToNav', setTabsToNav);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AppsNavBarParent
|
||||
sx={{
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
width: "60px",
|
||||
height: "unset",
|
||||
maxHeight: "70vh",
|
||||
borderRadius: "0px 30px 30px 0px",
|
||||
padding: "10px",
|
||||
position: 'relative',
|
||||
flexDirection: 'column',
|
||||
width: '60px',
|
||||
height: 'unset',
|
||||
maxHeight: '70vh',
|
||||
borderRadius: '0px 30px 30px 0px',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<AppsNavBarLeft
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent("devModeNavigateBack", selectedTab?.tabId);
|
||||
executeEvent('devModeNavigateBack', selectedTab?.tabId);
|
||||
}}
|
||||
disabled={isDisableBackButton}
|
||||
sx={{
|
||||
opacity: !isDisableBackButton ? 1 : 0.1,
|
||||
cursor: !isDisableBackButton ? 'pointer': 'default'
|
||||
cursor: !isDisableBackButton ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
<img src={NavBack} />
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
ref={tabsRef}
|
||||
@ -134,11 +113,11 @@ export const AppsDevModeNavBar = () => {
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons={true}
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
maxHeight: `320px`, // Ensure the tabs container fits within the available space
|
||||
overflow: "hidden", // Prevents overflow on small screens
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
{tabs?.map((tab) => (
|
||||
@ -153,65 +132,61 @@ export const AppsDevModeNavBar = () => {
|
||||
/>
|
||||
} // Pass custom component
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
padding: "0px",
|
||||
margin: "0px",
|
||||
minWidth: "0px",
|
||||
width: "50px",
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
minWidth: '0px',
|
||||
width: '50px',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</AppsNavBarLeft>
|
||||
|
||||
{selectedTab && (
|
||||
<AppsNavBarRight
|
||||
sx={{
|
||||
gap: "10px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent("devModeNewTabWindow", {});
|
||||
sx={{
|
||||
gap: '10px',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "40px",
|
||||
width: "40px",
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent('devModeNewTabWindow', {});
|
||||
}}
|
||||
src={NavAdd}
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if(selectedTab?.refreshFunc){
|
||||
selectedTab.refreshFunc(selectedTab?.tabId)
|
||||
} else {
|
||||
executeEvent("refreshApp", {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
|
||||
sx={{
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
>
|
||||
<NavAdd
|
||||
style={{
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
height: 'auto'
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (selectedTab?.refreshFunc) {
|
||||
selectedTab.refreshFunc(selectedTab?.tabId);
|
||||
} else {
|
||||
executeEvent('refreshApp', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
sx={{
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
width: '40px',
|
||||
height: 'auto',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
)}
|
||||
|
||||
|
||||
</AppsNavBarParent>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TabParent } from './Apps-styles';
|
||||
import NavCloseTab from '../../assets/svgs/NavCloseTab.svg';
|
||||
import { NavCloseTab } from '../../assets/Icons/NavCloseTab.tsx';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { Avatar, ButtonBase } from '@mui/material';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
@ -27,14 +27,13 @@ export const AppsDevModeTabComponent = ({ isSelected, app }) => {
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
<img
|
||||
<NavCloseTab
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-5px',
|
||||
right: '-5px',
|
||||
zIndex: 1,
|
||||
}}
|
||||
src={NavCloseTab}
|
||||
/>
|
||||
)}
|
||||
<Avatar
|
||||
|
@ -1,11 +1,4 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -13,7 +6,6 @@ import {
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsLibraryContainer,
|
||||
AppsParent,
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
@ -24,16 +16,22 @@ import {
|
||||
PublishQAppCTARight,
|
||||
PublishQAppDotsBG,
|
||||
} from './Apps-styles';
|
||||
import { Avatar, Box, ButtonBase, InputBase, styled } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
ButtonBase,
|
||||
InputBase,
|
||||
styled,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import IconSearch from '../../assets/svgs/Search.svg';
|
||||
import IconClearInput from '../../assets/svgs/ClearInput.svg';
|
||||
import qappDevelopText from '../../assets/svgs/qappDevelopText.svg';
|
||||
import qappDots from '../../assets/svgs/qappDots.svg';
|
||||
// import { Return } from './assets/svgs/Return.tsx';
|
||||
import ReturnSVG from '../../assets/svgs/Return.svg';
|
||||
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { AppInfoSnippet } from './AppInfoSnippet';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
@ -43,6 +41,7 @@ import {
|
||||
MailIconImg,
|
||||
ShowMessageReturnButton,
|
||||
} from '../Group/Forum/Mail-styles';
|
||||
|
||||
const officialAppList = [
|
||||
'q-tube',
|
||||
'q-blog',
|
||||
@ -56,10 +55,9 @@ const officialAppList = [
|
||||
'q-manager',
|
||||
'q-wallets',
|
||||
'q-search',
|
||||
'q-nodecontrol',
|
||||
];
|
||||
|
||||
// TODO: apply dark/light style
|
||||
|
||||
const ScrollerStyled = styled('div')({
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
'::-webkit-scrollbar': {
|
||||
@ -75,10 +73,10 @@ const ScrollerStyled = styled('div')({
|
||||
});
|
||||
|
||||
const StyledVirtuosoContainer = styled('div')({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
'::-webkit-scrollbar': {
|
||||
@ -147,6 +145,8 @@ export const AppsLibrary = ({
|
||||
);
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer
|
||||
sx={{
|
||||
@ -164,6 +164,7 @@ export const AppsLibrary = ({
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
@ -190,7 +191,9 @@ export const AppsLibrary = ({
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
<ShowMessageReturnButton
|
||||
sx={{
|
||||
padding: '2px',
|
||||
@ -199,10 +202,12 @@ export const AppsLibrary = ({
|
||||
executeEvent('navigateBack', {});
|
||||
}}
|
||||
>
|
||||
<MailIconImg src={ReturnSVG} />
|
||||
<MailIconImg src={ReturnSVG} /> // TODO return icon
|
||||
<ComposeP>Return to Apps Dashboard</ComposeP>
|
||||
</ShowMessageReturnButton>
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
{searchedList?.length > 0 ? (
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer
|
||||
@ -226,7 +231,9 @@ export const AppsLibrary = ({
|
||||
<>
|
||||
<AppsWidthLimiter>
|
||||
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppsContainer>
|
||||
{officialApps?.map((qapp) => {
|
||||
return (
|
||||
@ -270,6 +277,7 @@ export const AppsLibrary = ({
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>
|
||||
{qapp?.metadata?.title || qapp?.name}
|
||||
</AppCircleLabel>
|
||||
@ -278,20 +286,27 @@ export const AppsLibrary = ({
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
<AppLibrarySubTitle>
|
||||
{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
|
||||
</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
</AppsWidthLimiter>
|
||||
|
||||
<PublishQAppCTAParent>
|
||||
<PublishQAppCTALeft>
|
||||
<PublishQAppDotsBG>
|
||||
<img src={qappDots} />
|
||||
</PublishQAppDotsBG>
|
||||
|
||||
<Spacer width="29px" />
|
||||
|
||||
<img src={qappDevelopText} />
|
||||
</PublishQAppCTALeft>
|
||||
|
||||
<PublishQAppCTARight
|
||||
onClick={() => {
|
||||
setMode('publish');
|
||||
@ -300,13 +315,18 @@ export const AppsLibrary = ({
|
||||
<PublishQAppCTAButton>
|
||||
{hasPublishApp ? 'Update' : 'Publish'}
|
||||
</PublishQAppCTAButton>
|
||||
|
||||
<Spacer width="20px" />
|
||||
</PublishQAppCTARight>
|
||||
</PublishQAppCTAParent>
|
||||
|
||||
<AppsWidthLimiter>
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
|
||||
<AppsWidthLimiter
|
||||
sx={{
|
||||
flexDirection: 'row',
|
||||
@ -337,18 +357,17 @@ export const AppsLibrary = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '110px',
|
||||
width: '110px',
|
||||
background:
|
||||
'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
|
||||
color: '#1D1D1E',
|
||||
fontWeight: 700,
|
||||
fontSize: '16px',
|
||||
flexShrink: 0,
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '11px',
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
fontSize: '16px',
|
||||
fontWeight: 700,
|
||||
height: '110px',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
}}
|
||||
>
|
||||
{category?.name}
|
||||
|
@ -71,6 +71,7 @@ const officialAppList = [
|
||||
'q-mintership',
|
||||
'q-wallets',
|
||||
'q-search',
|
||||
'q-nodecontrol',
|
||||
];
|
||||
|
||||
const ScrollerStyled = styled('div')({
|
||||
@ -134,9 +135,9 @@ export const AppsLibraryDesktop = ({
|
||||
setDebouncedValue(searchValue);
|
||||
}, 350);
|
||||
setTimeout(() => {
|
||||
virtuosoRef.current.scrollToIndex({
|
||||
index: 0,
|
||||
});
|
||||
if (virtuosoRef.current) {
|
||||
virtuosoRef.current.scrollToIndex({ index: 0 });
|
||||
}
|
||||
}, 500);
|
||||
// Cleanup timeout if searchValue changes before the timeout completes
|
||||
return () => {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppsNavBarLeft,
|
||||
AppsNavBarParent,
|
||||
AppsNavBarRight,
|
||||
} from "./Apps-styles";
|
||||
import NavBack from "../../assets/svgs/NavBack.svg";
|
||||
import NavAdd from "../../assets/svgs/NavAdd.svg";
|
||||
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
|
||||
} from './Apps-styles';
|
||||
import { NavBack } from '../../assets/Icons/NavBack.tsx';
|
||||
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
|
||||
import { NavMoreMenu } from '../../assets/Icons/NavMoreMenu.tsx';
|
||||
import {
|
||||
ButtonBase,
|
||||
ListItemIcon,
|
||||
@ -15,27 +15,33 @@ import {
|
||||
MenuItem,
|
||||
Tab,
|
||||
Tabs,
|
||||
} from "@mui/material";
|
||||
} from '@mui/material';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import TabComponent from "./TabComponent";
|
||||
import PushPinIcon from "@mui/icons-material/PushPin";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
} from '../../utils/events';
|
||||
import TabComponent from './TabComponent';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
navigationControllerAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from "../../atoms/global";
|
||||
} from '../../atoms/global';
|
||||
|
||||
export function saveToLocalStorage(key, subKey, newValue, otherRootData = {}, deleteWholeKey) {
|
||||
export function saveToLocalStorage(
|
||||
key,
|
||||
subKey,
|
||||
newValue,
|
||||
otherRootData = {},
|
||||
deleteWholeKey
|
||||
) {
|
||||
try {
|
||||
if(deleteWholeKey){
|
||||
if (deleteWholeKey) {
|
||||
localStorage.setItem(key, null);
|
||||
return
|
||||
return;
|
||||
}
|
||||
// Fetch existing data
|
||||
const existingData = localStorage.getItem(key);
|
||||
@ -64,7 +70,7 @@ export function saveToLocalStorage(key, subKey, newValue, otherRootData = {}, de
|
||||
const serializedValue = JSON.stringify(combinedData);
|
||||
localStorage.setItem(key, serializedValue);
|
||||
} catch (error) {
|
||||
console.error("Error saving to localStorage:", error);
|
||||
console.error('Error saving to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,13 +84,17 @@ export const AppsNavBar = () => {
|
||||
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
|
||||
sortablePinnedAppsAtom
|
||||
);
|
||||
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
|
||||
const [navigationController, setNavigationController] = useRecoilState(
|
||||
navigationControllerAtom
|
||||
);
|
||||
|
||||
const isDisableBackButton = useMemo(()=> {
|
||||
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
|
||||
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
|
||||
return false
|
||||
}, [navigationController, selectedTab])
|
||||
const isDisableBackButton = useMemo(() => {
|
||||
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return false;
|
||||
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return true;
|
||||
return false;
|
||||
}, [navigationController, selectedTab]);
|
||||
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
@ -101,13 +111,13 @@ export const AppsNavBar = () => {
|
||||
useEffect(() => {
|
||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||
if (tabsRef.current) {
|
||||
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
|
||||
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
|
||||
if (tabElements.length > 0) {
|
||||
const lastTab = tabElements[tabElements.length - 1];
|
||||
lastTab.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "end",
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -122,10 +132,10 @@ export const AppsNavBar = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("setTabsToNav", setTabsToNav);
|
||||
subscribeToEvent('setTabsToNav', setTabsToNav);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("setTabsToNav", setTabsToNav);
|
||||
unsubscribeFromEvent('setTabsToNav', setTabsToNav);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -137,28 +147,29 @@ export const AppsNavBar = () => {
|
||||
<AppsNavBarParent>
|
||||
<AppsNavBarLeft>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent("navigateBack", selectedTab?.tabId);
|
||||
onClick={() => {
|
||||
executeEvent('navigateBack', selectedTab?.tabId);
|
||||
}}
|
||||
disabled={isDisableBackButton}
|
||||
sx={{
|
||||
opacity: !isDisableBackButton ? 1 : 0.1,
|
||||
cursor: !isDisableBackButton ? 'pointer': 'default'
|
||||
opacity: !isDisableBackButton ? 1 : 0.3,
|
||||
cursor: !isDisableBackButton ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
<img src={NavBack} />
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
|
||||
<Tabs
|
||||
ref={tabsRef}
|
||||
aria-label="basic tabs example"
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons={false}
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
maxWidth: `calc(100vw - 150px)`, // Ensure the tabs container fits within the available space
|
||||
overflow: "hidden", // Prevents overflow on small screens
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
{tabs?.map((tab) => (
|
||||
@ -173,83 +184,83 @@ export const AppsNavBar = () => {
|
||||
/>
|
||||
} // Pass custom component
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
padding: "0px",
|
||||
margin: "0px",
|
||||
minWidth: "0px",
|
||||
width: "50px",
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
minWidth: '0px',
|
||||
width: '50px',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</AppsNavBarLeft>
|
||||
|
||||
{selectedTab && (
|
||||
<AppsNavBarRight
|
||||
sx={{
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent("newTabWindow", {});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "40px",
|
||||
width: "40px",
|
||||
}}
|
||||
src={NavAdd}
|
||||
/>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (!selectedTab) return;
|
||||
handleClick(e);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "34px",
|
||||
width: "34px",
|
||||
}}
|
||||
src={NavMoreMenu}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
<AppsNavBarRight
|
||||
sx={{
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent('newTabWindow', {});
|
||||
}}
|
||||
>
|
||||
<NavAdd
|
||||
style={{
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (!selectedTab) return;
|
||||
handleClick(e);
|
||||
}}
|
||||
>
|
||||
<NavMoreMenu
|
||||
style={{
|
||||
height: '34px',
|
||||
width: '34px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
)}
|
||||
|
||||
|
||||
<Menu
|
||||
id="navbar-more-mobile"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "basic-button",
|
||||
'aria-labelledby': 'basic-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: "var(--bg-primary)",
|
||||
color: "#fff",
|
||||
width: "148px",
|
||||
borderRadius: "5px",
|
||||
backgroundColor: 'var(--bg-primary)',
|
||||
color: '#fff',
|
||||
width: '148px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "10px",
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
@ -280,8 +291,8 @@ export const AppsNavBar = () => {
|
||||
}
|
||||
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings",
|
||||
"sortablePinnedApps",
|
||||
'ext_saved_settings',
|
||||
'sortablePinnedApps',
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
@ -293,31 +304,33 @@ export const AppsNavBar = () => {
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "24px !important",
|
||||
marginRight: "5px",
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<PushPinIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: isSelectedAppPinned ? "red" : "rgba(250, 250, 250, 0.5)",
|
||||
color: isSelectedAppPinned ? 'red' : 'rgba(250, 250, 250, 0.5)',
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: isSelectedAppPinned ? "red" : "rgba(250, 250, 250, 0.5)",
|
||||
color: isSelectedAppPinned ? 'red' : 'rgba(250, 250, 250, 0.5)',
|
||||
},
|
||||
}}
|
||||
primary={`${isSelectedAppPinned ? "Unpin app" : "Pin app"}`}
|
||||
primary={`${isSelectedAppPinned ? 'Unpin app' : 'Pin app'}`}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
executeEvent("refreshApp", {
|
||||
executeEvent('refreshApp', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
handleClose();
|
||||
@ -325,23 +338,24 @@ export const AppsNavBar = () => {
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "24px !important",
|
||||
marginRight: "5px",
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
},
|
||||
}}
|
||||
primary="Refresh"
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppsNavBarLeft,
|
||||
AppsNavBarParent,
|
||||
AppsNavBarRight,
|
||||
} from "./Apps-styles";
|
||||
import NavBack from "../../assets/svgs/NavBack.svg";
|
||||
import NavAdd from "../../assets/svgs/NavAdd.svg";
|
||||
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
|
||||
} from './Apps-styles';
|
||||
import { NavBack } from '../../assets/Icons/NavBack.tsx';
|
||||
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
|
||||
import { NavMoreMenu } from '../../assets/Icons/NavMoreMenu.tsx';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import {
|
||||
ButtonBase,
|
||||
@ -16,21 +16,22 @@ import {
|
||||
MenuItem,
|
||||
Tab,
|
||||
Tabs,
|
||||
} from "@mui/material";
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import TabComponent from "./TabComponent";
|
||||
import PushPinIcon from "@mui/icons-material/PushPin";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
} from '../../utils/events';
|
||||
import TabComponent from './TabComponent';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
navigationControllerAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from "../../atoms/global";
|
||||
} from '../../atoms/global';
|
||||
|
||||
export function saveToLocalStorage(key, subKey, newValue) {
|
||||
try {
|
||||
@ -59,14 +60,17 @@ export function saveToLocalStorage(key, subKey, newValue) {
|
||||
const serializedValue = JSON.stringify(combinedData);
|
||||
localStorage.setItem(key, serializedValue);
|
||||
} catch (error) {
|
||||
console.error("Error saving to localStorage:", error);
|
||||
console.error('Error saving to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
export const AppsNavBarDesktop = ({ disableBack }) => {
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [selectedTab, setSelectedTab] = useState(null);
|
||||
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
|
||||
const [navigationController, setNavigationController] = useRecoilState(
|
||||
navigationControllerAtom
|
||||
);
|
||||
const theme = useTheme();
|
||||
|
||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||
const tabsRef = useRef(null);
|
||||
@ -76,7 +80,6 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
sortablePinnedAppsAtom
|
||||
);
|
||||
|
||||
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
);
|
||||
@ -92,29 +95,26 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
useEffect(() => {
|
||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||
if (tabsRef.current) {
|
||||
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
|
||||
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
|
||||
if (tabElements.length > 0) {
|
||||
const lastTab = tabElements[tabElements.length - 1];
|
||||
lastTab.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "end",
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'end',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [tabs.length]); // Dependency on the number of tabs
|
||||
|
||||
|
||||
|
||||
const isDisableBackButton = useMemo(()=> {
|
||||
if(disableBack) return true
|
||||
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
|
||||
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
|
||||
return false
|
||||
}, [navigationController, selectedTab, disableBack])
|
||||
|
||||
|
||||
|
||||
const isDisableBackButton = useMemo(() => {
|
||||
if (disableBack) return true;
|
||||
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return false;
|
||||
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
|
||||
return true;
|
||||
return false;
|
||||
}, [navigationController, selectedTab, disableBack]);
|
||||
|
||||
const setTabsToNav = (e) => {
|
||||
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
|
||||
@ -124,57 +124,61 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("setTabsToNav", setTabsToNav);
|
||||
subscribeToEvent('setTabsToNav', setTabsToNav);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("setTabsToNav", setTabsToNav);
|
||||
unsubscribeFromEvent('setTabsToNav', setTabsToNav);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const isSelectedAppPinned = useMemo(()=> {
|
||||
if(selectedTab?.isPrivate){
|
||||
const isSelectedAppPinned = useMemo(() => {
|
||||
if (selectedTab?.isPrivate) {
|
||||
return !!sortablePinnedApps?.find(
|
||||
(item) =>
|
||||
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name && item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service && item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
|
||||
item?.privateAppProperties?.name ===
|
||||
selectedTab?.privateAppProperties?.name &&
|
||||
item?.privateAppProperties?.service ===
|
||||
selectedTab?.privateAppProperties?.service &&
|
||||
item?.privateAppProperties?.identifier ===
|
||||
selectedTab?.privateAppProperties?.identifier
|
||||
);
|
||||
} else {
|
||||
return !!sortablePinnedApps?.find(
|
||||
(item) =>
|
||||
item?.name === selectedTab?.name && item?.service === selectedTab?.service
|
||||
item?.name === selectedTab?.name &&
|
||||
item?.service === selectedTab?.service
|
||||
);
|
||||
}
|
||||
}, [selectedTab,sortablePinnedApps])
|
||||
}, [selectedTab, sortablePinnedApps]);
|
||||
|
||||
return (
|
||||
<AppsNavBarParent
|
||||
sx={{
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
width: "60px",
|
||||
height: "unset",
|
||||
maxHeight: "70vh",
|
||||
borderRadius: "0px 30px 30px 0px",
|
||||
padding: "10px",
|
||||
borderRadius: '0px 30px 30px 0px',
|
||||
flexDirection: 'column',
|
||||
height: 'unset',
|
||||
maxHeight: '70vh',
|
||||
padding: '10px',
|
||||
position: 'relative',
|
||||
width: '60px',
|
||||
}}
|
||||
>
|
||||
<AppsNavBarLeft
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent("navigateBack", selectedTab?.tabId);
|
||||
executeEvent('navigateBack', selectedTab?.tabId);
|
||||
}}
|
||||
disabled={isDisableBackButton}
|
||||
sx={{
|
||||
opacity: !isDisableBackButton ? 1 : 0.1,
|
||||
cursor: !isDisableBackButton ? 'pointer': 'default'
|
||||
cursor: !isDisableBackButton ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
<img src={NavBack} />
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
@ -183,11 +187,11 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons={true}
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
maxHeight: `320px`, // Ensure the tabs container fits within the available space
|
||||
overflow: "hidden", // Prevents overflow on small screens
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
{tabs?.map((tab) => (
|
||||
@ -202,84 +206,83 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
/>
|
||||
} // Pass custom component
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
padding: "0px",
|
||||
margin: "0px",
|
||||
minWidth: "0px",
|
||||
width: "50px",
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
minWidth: '0px',
|
||||
width: '50px',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</AppsNavBarLeft>
|
||||
|
||||
{selectedTab && (
|
||||
<AppsNavBarRight
|
||||
sx={{
|
||||
gap: "10px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent("newTabWindow", {});
|
||||
sx={{
|
||||
gap: '10px',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "40px",
|
||||
width: "40px",
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSelectedTab(null);
|
||||
executeEvent('newTabWindow', {});
|
||||
}}
|
||||
src={NavAdd}
|
||||
/>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (!selectedTab) return;
|
||||
handleClick(e);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
height: "34px",
|
||||
width: "34px",
|
||||
>
|
||||
<NavAdd
|
||||
style={{
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if (!selectedTab) return;
|
||||
handleClick(e);
|
||||
}}
|
||||
src={NavMoreMenu}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
>
|
||||
<NavMoreMenu
|
||||
style={{
|
||||
height: '34px',
|
||||
width: '34px',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
)}
|
||||
|
||||
|
||||
<Menu
|
||||
id="navbar-more-mobile"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "basic-button",
|
||||
'aria-labelledby': 'basic-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: "var(--bg-primary)",
|
||||
color: "#fff",
|
||||
width: "148px",
|
||||
borderRadius: "5px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
width: '148px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "10px",
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
@ -291,13 +294,16 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
|
||||
if (isSelectedAppPinned) {
|
||||
// Remove the selected app if it is pinned
|
||||
if(selectedTab?.isPrivate){
|
||||
if (selectedTab?.isPrivate) {
|
||||
updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name &&
|
||||
item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service &&
|
||||
item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
|
||||
item?.privateAppProperties?.name ===
|
||||
selectedTab?.privateAppProperties?.name &&
|
||||
item?.privateAppProperties?.service ===
|
||||
selectedTab?.privateAppProperties?.service &&
|
||||
item?.privateAppProperties?.identifier ===
|
||||
selectedTab?.privateAppProperties?.identifier
|
||||
)
|
||||
);
|
||||
} else {
|
||||
@ -309,21 +315,19 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Add the selected app if it is not pinned
|
||||
if(selectedTab?.isPrivate){
|
||||
if (selectedTab?.isPrivate) {
|
||||
updatedApps = [
|
||||
...prev,
|
||||
{
|
||||
isPreview: true,
|
||||
isPrivate: true,
|
||||
privateAppProperties: {
|
||||
...(selectedTab?.privateAppProperties || {})
|
||||
}
|
||||
|
||||
},
|
||||
];
|
||||
...prev,
|
||||
{
|
||||
isPreview: true,
|
||||
isPrivate: true,
|
||||
privateAppProperties: {
|
||||
...(selectedTab?.privateAppProperties || {}),
|
||||
},
|
||||
},
|
||||
];
|
||||
} else {
|
||||
updatedApps = [
|
||||
...prev,
|
||||
@ -333,12 +337,11 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings",
|
||||
"sortablePinnedApps",
|
||||
'ext_saved_settings',
|
||||
'sortablePinnedApps',
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
@ -350,70 +353,74 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "24px !important",
|
||||
marginRight: "5px",
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<PushPinIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: isSelectedAppPinned ? "var(--danger)" : "rgba(250, 250, 250, 0.5)",
|
||||
color: isSelectedAppPinned
|
||||
? 'var(--danger)'
|
||||
: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: isSelectedAppPinned ? "var(--danger)" : "rgba(250, 250, 250, 0.5)",
|
||||
color: isSelectedAppPinned
|
||||
? 'var(--danger)'
|
||||
: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
primary={`${isSelectedAppPinned ? "Unpin app" : "Pin app"}`}
|
||||
primary={`${isSelectedAppPinned ? 'Unpin app' : 'Pin app'}`}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
if (selectedTab?.refreshFunc) {
|
||||
selectedTab.refreshFunc(selectedTab?.tabId);
|
||||
|
||||
} else {
|
||||
executeEvent("refreshApp", {
|
||||
executeEvent('refreshApp', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "24px !important",
|
||||
marginRight: "5px",
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
primary="Refresh"
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
{!selectedTab?.isPrivate && (
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
executeEvent("copyLink", {
|
||||
executeEvent('copyLink', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
handleClose();
|
||||
@ -421,23 +428,24 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: "24px !important",
|
||||
marginRight: "5px",
|
||||
minWidth: '24px !important',
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
<ContentCopyIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "12px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
primary="Copy link"
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
@ -13,14 +12,17 @@ import {
|
||||
Select,
|
||||
Tab,
|
||||
Tabs,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
import { groupsPropertiesAtom, myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
||||
import { Label } from "../Group/AddGroup";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useHandlePrivateApps } from './useHandlePrivateApps';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import {
|
||||
groupsPropertiesAtom,
|
||||
myGroupsWhereIAmAdminAtom,
|
||||
} from '../../atoms/global';
|
||||
import { Label } from '../Group/AddGroup';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import {
|
||||
Add,
|
||||
AppCircle,
|
||||
@ -28,52 +30,59 @@ import {
|
||||
AppCircleLabel,
|
||||
PublishQAppChoseFile,
|
||||
PublishQAppInfo,
|
||||
} from "./Apps-styles";
|
||||
import ImageUploader from "../../common/ImageUploader";
|
||||
import { isMobile, MyContext } from "../../App";
|
||||
import { fileToBase64 } from "../../utils/fileReading";
|
||||
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
||||
import { getFee } from "../../background";
|
||||
} from './Apps-styles';
|
||||
import ImageUploader from '../../common/ImageUploader';
|
||||
import { isMobile, MyContext } from '../../App';
|
||||
import { fileToBase64 } from '../../utils/fileReading';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import { getFee } from '../../background';
|
||||
|
||||
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||
|
||||
export const AppsPrivate = ({myName}) => {
|
||||
export const AppsPrivate = ({ myName }) => {
|
||||
const { openApp } = useHandlePrivateApps();
|
||||
const [file, setFile] = useState(null);
|
||||
const [logo, setLogo] = useState(null);
|
||||
const [qortalUrl, setQortalUrl] = useState("");
|
||||
const [qortalUrl, setQortalUrl] = useState('');
|
||||
const [selectedGroup, setSelectedGroup] = useState(0);
|
||||
const [groupsProperties] = useRecoilState(groupsPropertiesAtom)
|
||||
const [groupsProperties] = useRecoilState(groupsPropertiesAtom);
|
||||
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
|
||||
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
|
||||
myGroupsWhereIAmAdminAtom
|
||||
);
|
||||
|
||||
const myGroupsWhereIAmAdmin = useMemo(()=> {
|
||||
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
|
||||
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
|
||||
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext);
|
||||
|
||||
const myGroupsWhereIAmAdmin = useMemo(() => {
|
||||
return myGroupsWhereIAmAdminFromGlobal?.filter(
|
||||
(group) => groupsProperties[group?.groupId]?.isOpen === false
|
||||
);
|
||||
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties]);
|
||||
|
||||
const myGroupsPrivate = useMemo(()=> {
|
||||
return memberGroups?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||
}, [memberGroups, groupsProperties])
|
||||
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
|
||||
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } =
|
||||
useContext(MyContext);
|
||||
const theme = useTheme();
|
||||
|
||||
const myGroupsPrivate = useMemo(() => {
|
||||
return memberGroups?.filter(
|
||||
(group) => groupsProperties[group?.groupId]?.isOpen === false
|
||||
);
|
||||
}, [memberGroups, groupsProperties]);
|
||||
const [privateAppValues, setPrivateAppValues] = useState({
|
||||
name: "",
|
||||
service: "DOCUMENT",
|
||||
identifier: "",
|
||||
name: '',
|
||||
service: 'DOCUMENT',
|
||||
identifier: '',
|
||||
groupId: 0,
|
||||
});
|
||||
|
||||
const [newPrivateAppValues, setNewPrivateAppValues] = useState({
|
||||
service: "DOCUMENT",
|
||||
identifier: "",
|
||||
name: "",
|
||||
service: 'DOCUMENT',
|
||||
identifier: '',
|
||||
name: '',
|
||||
});
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
accept: {
|
||||
"application/zip": [".zip"], // Only accept zip files
|
||||
'application/zip': ['.zip'], // Only accept zip files
|
||||
},
|
||||
maxSize: maxFileSize,
|
||||
multiple: false, // Disable multiple file uploads
|
||||
@ -85,7 +94,7 @@ export const AppsPrivate = ({myName}) => {
|
||||
onDropRejected: (fileRejections) => {
|
||||
fileRejections.forEach(({ file, errors }) => {
|
||||
errors.forEach((error) => {
|
||||
if (error.code === "file-too-large") {
|
||||
if (error.code === 'file-too-large') {
|
||||
console.error(
|
||||
`File ${file.name} is too large. Max size allowed is ${
|
||||
maxFileSize / (1024 * 1024)
|
||||
@ -100,25 +109,24 @@ export const AppsPrivate = ({myName}) => {
|
||||
const addPrivateApp = async () => {
|
||||
try {
|
||||
if (privateAppValues?.groupId === 0) return;
|
||||
|
||||
await openApp(privateAppValues, true);
|
||||
|
||||
await openApp(privateAppValues, true);
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const clearFields = () => {
|
||||
setPrivateAppValues({
|
||||
name: "",
|
||||
service: "DOCUMENT",
|
||||
identifier: "",
|
||||
name: '',
|
||||
service: 'DOCUMENT',
|
||||
identifier: '',
|
||||
groupId: 0,
|
||||
});
|
||||
setNewPrivateAppValues({
|
||||
service: "DOCUMENT",
|
||||
identifier: "",
|
||||
name: "",
|
||||
service: 'DOCUMENT',
|
||||
identifier: '',
|
||||
name: '',
|
||||
});
|
||||
setFile(null);
|
||||
setValueTabPrivateApp(0);
|
||||
@ -129,9 +137,9 @@ export const AppsPrivate = ({myName}) => {
|
||||
const publishPrivateApp = async () => {
|
||||
try {
|
||||
if (selectedGroup === 0) return;
|
||||
if (!logo) throw new Error("Please select an image for a logo");
|
||||
if (!myName) throw new Error("You need a Qortal name to publish");
|
||||
if (!newPrivateAppValues?.name) throw new Error("Your app needs a name");
|
||||
if (!logo) throw new Error('Please select an image for a logo');
|
||||
if (!myName) throw new Error('You need a Qortal name to publish');
|
||||
if (!newPrivateAppValues?.name) throw new Error('Your app needs a name');
|
||||
const base64Logo = await fileToBase64(logo);
|
||||
const base64App = await fileToBase64(file);
|
||||
const objectToSave = {
|
||||
@ -141,27 +149,29 @@ export const AppsPrivate = ({myName}) => {
|
||||
};
|
||||
const object64 = await objectToBase64(objectToSave);
|
||||
const decryptedData = await window.sendMessage(
|
||||
"ENCRYPT_QORTAL_GROUP_DATA",
|
||||
'ENCRYPT_QORTAL_GROUP_DATA',
|
||||
|
||||
{
|
||||
base64: object64,
|
||||
groupId: selectedGroup,
|
||||
}
|
||||
);
|
||||
|
||||
if (decryptedData?.error) {
|
||||
throw new Error(
|
||||
decryptedData?.error || "Unable to encrypt app. App not published"
|
||||
decryptedData?.error || 'Unable to encrypt app. App not published'
|
||||
);
|
||||
}
|
||||
const fee = await getFee("ARBITRARY");
|
||||
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
await show({
|
||||
message: "Would you like to publish this app?",
|
||||
publishFee: fee.fee + " QORT",
|
||||
message: 'Would you like to publish this app?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage("publishOnQDN", {
|
||||
.sendMessage('publishOnQDN', {
|
||||
data: decryptedData,
|
||||
identifier: newPrivateAppValues?.identifier,
|
||||
service: newPrivateAppValues?.service,
|
||||
@ -174,9 +184,10 @@ export const AppsPrivate = ({myName}) => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
openApp(
|
||||
{
|
||||
identifier: newPrivateAppValues?.identifier,
|
||||
@ -188,10 +199,10 @@ export const AppsPrivate = ({myName}) => {
|
||||
);
|
||||
clearFields();
|
||||
} catch (error) {
|
||||
setOpenSnackGlobal(true)
|
||||
setOpenSnackGlobal(true);
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to publish app",
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to publish app',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -203,9 +214,10 @@ export const AppsPrivate = ({myName}) => {
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonBase
|
||||
@ -213,17 +225,18 @@ export const AppsPrivate = ({myName}) => {
|
||||
setIsOpenPrivateModal(true);
|
||||
}}
|
||||
sx={{
|
||||
width: "80px",
|
||||
width: '80px',
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
gap: !isMobile ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
|
||||
<AppCircleLabel>Private</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
@ -233,7 +246,7 @@ export const AppsPrivate = ({myName}) => {
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (e.key === 'Enter') {
|
||||
if (valueTabPrivateApp === 0) {
|
||||
if (
|
||||
!privateAppValues.name ||
|
||||
@ -249,23 +262,17 @@ export const AppsPrivate = ({myName}) => {
|
||||
maxWidth="md"
|
||||
fullWidth={true}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{valueTabPrivateApp === 0
|
||||
? "Access private app"
|
||||
: "Publish private app"}
|
||||
</DialogTitle>
|
||||
|
||||
<Box>
|
||||
<Tabs
|
||||
value={valueTabPrivateApp}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant={isMobile ? "scrollable" : "fullWidth"} // Scrollable on mobile, full width on desktop
|
||||
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
|
||||
scrollButtons="auto"
|
||||
allowScrollButtonsMobile
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
@ -273,20 +280,20 @@ export const AppsPrivate = ({myName}) => {
|
||||
label="Access app"
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Publish app"
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
@ -296,9 +303,9 @@ export const AppsPrivate = ({myName}) => {
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Select a group</Label>
|
||||
@ -333,10 +340,10 @@ export const AppsPrivate = ({myName}) => {
|
||||
<Spacer height="10px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
marginTop: "15px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
marginTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Label>name</Label>
|
||||
@ -355,10 +362,10 @@ export const AppsPrivate = ({myName}) => {
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
marginTop: "15px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
marginTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Label>identifier</Label>
|
||||
@ -376,6 +383,7 @@ export const AppsPrivate = ({myName}) => {
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
@ -406,15 +414,19 @@ export const AppsPrivate = ({myName}) => {
|
||||
<DialogContent>
|
||||
<PublishQAppInfo
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Select .zip file containing static content:{" "}
|
||||
Select .zip file containing static content:{' '}
|
||||
</PublishQAppInfo>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<PublishQAppInfo
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>{`
|
||||
50mb MB maximum`}</PublishQAppInfo>
|
||||
@ -426,17 +438,26 @@ export const AppsPrivate = ({myName}) => {
|
||||
)}
|
||||
|
||||
<Spacer height="18px" />
|
||||
<PublishQAppChoseFile {...getRootProps()}>
|
||||
{" "}
|
||||
|
||||
<PublishQAppChoseFile
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
{...getRootProps()}
|
||||
>
|
||||
{' '}
|
||||
<input {...getInputProps()} />
|
||||
{file ? "Change" : "Choose"} File
|
||||
{file ? 'Change' : 'Choose'} File
|
||||
</PublishQAppChoseFile>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Select a group</Label>
|
||||
@ -462,14 +483,15 @@ export const AppsPrivate = ({myName}) => {
|
||||
})}
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
marginTop: "15px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
marginTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Label>identifier</Label>
|
||||
@ -486,13 +508,15 @@ export const AppsPrivate = ({myName}) => {
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
marginTop: "15px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
marginTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Label>App name</Label>
|
||||
@ -511,12 +535,15 @@ export const AppsPrivate = ({myName}) => {
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<ImageUploader onPick={(file) => setLogo(file)}>
|
||||
<Button variant="contained">Choose logo</Button>
|
||||
</ImageUploader>
|
||||
|
||||
{logo?.name}
|
||||
<Spacer height="25px" />
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
@ -527,6 +554,7 @@ export const AppsPrivate = ({myName}) => {
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
!newPrivateAppValues.name ||
|
||||
|
@ -1,205 +1,251 @@
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { DndContext, closestCenter } from '@dnd-kit/core';
|
||||
import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable } from '@dnd-kit/sortable';
|
||||
import { KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
} from '@dnd-kit/sortable';
|
||||
import {
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { Avatar, ButtonBase } from '@mui/material';
|
||||
import { AppCircle, AppCircleContainer, AppCircleLabel } from './Apps-styles';
|
||||
import { getBaseApiReact, MyContext } from '../../App';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
||||
import {
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from '../../atoms/global';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { saveToLocalStorage } from './AppsNavBar';
|
||||
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import { useHandlePrivateApps } from './useHandlePrivateApps';
|
||||
|
||||
const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
const {openApp} = useHandlePrivateApps()
|
||||
const { openApp } = useHandlePrivateApps();
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
marginBottom: '5px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#f9f9f9',
|
||||
cursor: 'grab',
|
||||
color: 'black'
|
||||
};
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id });
|
||||
|
||||
return (
|
||||
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
|
||||
<ButtonBase
|
||||
ref={setNodeRef} {...attributes} {...listeners}
|
||||
sx={{
|
||||
width: "80px",
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}}
|
||||
onClick={async ()=> {
|
||||
if(app?.isPrivate){
|
||||
try {
|
||||
await openApp(app?.privateAppProperties)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
} else {
|
||||
executeEvent("addTab", {
|
||||
data: app
|
||||
})
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer sx={{
|
||||
border: "none",
|
||||
gap: isDesktop ? '10px': '5px'
|
||||
}}>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
|
||||
<LockIcon
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
}
|
||||
}}
|
||||
alt={app?.metadata?.title || app?.name}
|
||||
src={ app?.privateAppProperties?.logo ? app?.privateAppProperties?.logo :`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
}}
|
||||
// src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
|
||||
</AppCircle>
|
||||
{app?.isPrivate ? (
|
||||
<AppCircleLabel>
|
||||
{`${app?.privateAppProperties?.appName || "Private"}`}
|
||||
</AppCircleLabel>
|
||||
) : (
|
||||
<AppCircleLabel>
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppCircleLabel>
|
||||
)}
|
||||
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
</ContextMenuPinnedApps>
|
||||
);
|
||||
};
|
||||
const style = {
|
||||
backgroundColor: '#f9f9f9',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
color: 'black',
|
||||
cursor: 'grab',
|
||||
marginBottom: '5px',
|
||||
padding: '10px',
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
export const SortablePinnedApps = ({ isDesktop, myWebsite, myApp, availableQapps = [] }) => {
|
||||
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
|
||||
|
||||
const transformPinnedApps = useMemo(() => {
|
||||
|
||||
// Clone the existing pinned apps list
|
||||
let pinned = [...pinnedApps];
|
||||
|
||||
// Function to add or update `isMine` property
|
||||
const addOrUpdateIsMine = (pinnedList, appToCheck) => {
|
||||
if (!appToCheck) return pinnedList;
|
||||
|
||||
const existingIndex = pinnedList.findIndex(
|
||||
(item) => item?.service === appToCheck?.service && item?.name === appToCheck?.name
|
||||
);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// If the app is already in the list, update it with `isMine: true`
|
||||
pinnedList[existingIndex] = { ...pinnedList[existingIndex], isMine: true };
|
||||
return (
|
||||
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
|
||||
<ButtonBase
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
sx={{
|
||||
width: '80px',
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}}
|
||||
onClick={async () => {
|
||||
if (app?.isPrivate) {
|
||||
try {
|
||||
await openApp(app?.privateAppProperties);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
// If not in the list, add it with `isMine: true` at the beginning
|
||||
pinnedList.unshift({ ...appToCheck, isMine: true });
|
||||
executeEvent('addTab', {
|
||||
data: app,
|
||||
});
|
||||
}
|
||||
|
||||
return pinnedList;
|
||||
};
|
||||
|
||||
// Update or add `myWebsite` and `myApp` while preserving their positions
|
||||
pinned = addOrUpdateIsMine(pinned, myWebsite);
|
||||
pinned = addOrUpdateIsMine(pinned, myApp);
|
||||
|
||||
// Update pinned list based on availableQapps
|
||||
pinned = pinned.map((pin) => {
|
||||
const findIndex = availableQapps?.findIndex(
|
||||
(item) => item?.service === pin?.service && item?.name === pin?.name
|
||||
);
|
||||
if (findIndex !== -1) return {
|
||||
...availableQapps[findIndex],
|
||||
...pin
|
||||
}
|
||||
|
||||
return pin;
|
||||
});
|
||||
|
||||
return pinned;
|
||||
}, [myApp, myWebsite, pinnedApps, availableQapps]);
|
||||
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 10, // Set a distance to avoid triggering drag on small movements
|
||||
},
|
||||
}),
|
||||
useSensor(TouchSensor, {
|
||||
activationConstraint: {
|
||||
distance: 10, // Also apply to touch
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (!over) return; // Make sure the drop target exists
|
||||
|
||||
if (active.id !== over.id) {
|
||||
const oldIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === active.id);
|
||||
const newIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === over.id);
|
||||
|
||||
const newOrder = arrayMove(transformPinnedApps, oldIndex, newIndex);
|
||||
setPinnedApps(newOrder);
|
||||
saveToLocalStorage('ext_saved_settings','sortablePinnedApps', newOrder)
|
||||
setSettingsLocalLastUpdated(Date.now())
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={transformPinnedApps.map((app) => `${app?.service}-${app?.name}`)}>
|
||||
{transformPinnedApps.map((app) => (
|
||||
<SortableItem isDesktop={isDesktop} key={`${app?.service}-${app?.name}`} id={`${app?.service}-${app?.name}`} name={app?.name} app={app} />
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
border: 'none',
|
||||
gap: isDesktop ? '10px' : '5px',
|
||||
}}
|
||||
>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
|
||||
<LockIcon
|
||||
sx={{
|
||||
height: '42px',
|
||||
width: '42px',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: '42px',
|
||||
width: '42px',
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
},
|
||||
}}
|
||||
alt={app?.metadata?.title || app?.name}
|
||||
src={
|
||||
app?.privateAppProperties?.logo
|
||||
? app?.privateAppProperties?.logo
|
||||
: `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`
|
||||
}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '31px',
|
||||
height: 'auto',
|
||||
}}
|
||||
// src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
</AppCircle>
|
||||
{app?.isPrivate ? (
|
||||
<AppCircleLabel>
|
||||
{`${app?.privateAppProperties?.appName || 'Private'}`}
|
||||
</AppCircleLabel>
|
||||
) : (
|
||||
<AppCircleLabel>{app?.metadata?.title || app?.name}</AppCircleLabel>
|
||||
)}
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
</ContextMenuPinnedApps>
|
||||
);
|
||||
};
|
||||
|
||||
export const SortablePinnedApps = ({
|
||||
isDesktop,
|
||||
myWebsite,
|
||||
myApp,
|
||||
availableQapps = [],
|
||||
}) => {
|
||||
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
);
|
||||
|
||||
const transformPinnedApps = useMemo(() => {
|
||||
// Clone the existing pinned apps list
|
||||
let pinned = [...pinnedApps];
|
||||
|
||||
// Function to add or update `isMine` property
|
||||
const addOrUpdateIsMine = (pinnedList, appToCheck) => {
|
||||
if (!appToCheck) return pinnedList;
|
||||
|
||||
const existingIndex = pinnedList.findIndex(
|
||||
(item) =>
|
||||
item?.service === appToCheck?.service &&
|
||||
item?.name === appToCheck?.name
|
||||
);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// If the app is already in the list, update it with `isMine: true`
|
||||
pinnedList[existingIndex] = {
|
||||
...pinnedList[existingIndex],
|
||||
isMine: true,
|
||||
};
|
||||
} else {
|
||||
// If not in the list, add it with `isMine: true` at the beginning
|
||||
pinnedList.unshift({ ...appToCheck, isMine: true });
|
||||
}
|
||||
|
||||
return pinnedList;
|
||||
};
|
||||
|
||||
// Update or add `myWebsite` and `myApp` while preserving their positions
|
||||
pinned = addOrUpdateIsMine(pinned, myWebsite);
|
||||
pinned = addOrUpdateIsMine(pinned, myApp);
|
||||
|
||||
// Update pinned list based on availableQapps
|
||||
pinned = pinned.map((pin) => {
|
||||
const findIndex = availableQapps?.findIndex(
|
||||
(item) => item?.service === pin?.service && item?.name === pin?.name
|
||||
);
|
||||
if (findIndex !== -1)
|
||||
return {
|
||||
...availableQapps[findIndex],
|
||||
...pin,
|
||||
};
|
||||
|
||||
return pin;
|
||||
});
|
||||
|
||||
return pinned;
|
||||
}, [myApp, myWebsite, pinnedApps, availableQapps]);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 10, // Set a distance to avoid triggering drag on small movements
|
||||
},
|
||||
}),
|
||||
useSensor(TouchSensor, {
|
||||
activationConstraint: {
|
||||
distance: 10, // Also apply to touch
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (!over) return; // Make sure the drop target exists
|
||||
|
||||
if (active.id !== over.id) {
|
||||
const oldIndex = transformPinnedApps.findIndex(
|
||||
(item) => `${item?.service}-${item?.name}` === active.id
|
||||
);
|
||||
const newIndex = transformPinnedApps.findIndex(
|
||||
(item) => `${item?.service}-${item?.name}` === over.id
|
||||
);
|
||||
|
||||
const newOrder = arrayMove(transformPinnedApps, oldIndex, newIndex);
|
||||
setPinnedApps(newOrder);
|
||||
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', newOrder);
|
||||
setSettingsLocalLastUpdated(Date.now());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={transformPinnedApps.map((app) => `${app?.service}-${app?.name}`)}
|
||||
>
|
||||
{transformPinnedApps.map((app) => (
|
||||
<SortableItem
|
||||
isDesktop={isDesktop}
|
||||
key={`${app?.service}-${app?.name}`}
|
||||
id={`${app?.service}-${app?.name}`}
|
||||
name={app?.name}
|
||||
app={app}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
@ -1,54 +1,57 @@
|
||||
import { TabParent } from "./Apps-styles";
|
||||
import NavCloseTab from "../../assets/svgs/NavCloseTab.svg";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import { Avatar, ButtonBase } from "@mui/material";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import { TabParent } from './Apps-styles';
|
||||
import { NavCloseTab } from '../../assets/Icons/NavCloseTab.tsx';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { Avatar, ButtonBase, useTheme } from '@mui/material';
|
||||
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
|
||||
const TabComponent = ({ isSelected, app }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
executeEvent("removeTab", {
|
||||
executeEvent('removeTab', {
|
||||
data: app,
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent("setSelectedTab", {
|
||||
executeEvent('setSelectedTab', {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<TabParent
|
||||
sx={{
|
||||
border: isSelected && "1px solid #FFFFFF",
|
||||
borderStyle: isSelected && 'solid',
|
||||
borderWidth: isSelected && '1px',
|
||||
borderColor: isSelected && theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
<img
|
||||
<NavCloseTab
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
right: "-5px",
|
||||
position: 'absolute',
|
||||
top: '-5px',
|
||||
right: '-5px',
|
||||
zIndex: 1,
|
||||
}}
|
||||
src={NavCloseTab}
|
||||
/>
|
||||
)}
|
||||
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
|
||||
<LockIcon
|
||||
sx={{
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
}}
|
||||
alt={app?.name}
|
||||
src={
|
||||
@ -61,8 +64,8 @@ const TabComponent = ({ isSelected, app }) => {
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "28px",
|
||||
height: "auto",
|
||||
width: '28px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
|
@ -1,154 +1,154 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Input,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
List,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Label } from './Group/AddGroup';
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
List,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Spacer } from '../common/Spacer';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApiReact, MyContext } from '../App';
|
||||
import { getFee } from '../background';
|
||||
import qTradeLogo from "../assets/Icons/q-trade-logo.webp";
|
||||
|
||||
import qTradeLogo from '../assets/Icons/q-trade-logo.webp';
|
||||
import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked';
|
||||
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../utils/events';
|
||||
import { BarSpinner } from '../common/Spinners/BarSpinner/BarSpinner';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../utils/events';
|
||||
|
||||
export const BuyQortInformation = ({ balance }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const openBuyQortInfoFunc = useCallback(
|
||||
(e) => {
|
||||
setIsOpen(true);
|
||||
},
|
||||
[setIsOpen]
|
||||
);
|
||||
|
||||
export const BuyQortInformation = ({balance}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const theme = useTheme();
|
||||
|
||||
const openBuyQortInfoFunc = useCallback((e) => {
|
||||
setIsOpen(true)
|
||||
|
||||
}, [ setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("openBuyQortInfo", openBuyQortInfoFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("openBuyQortInfo", openBuyQortInfoFunc);
|
||||
};
|
||||
}, [openBuyQortInfoFunc]);
|
||||
useEffect(() => {
|
||||
subscribeToEvent('openBuyQortInfo', openBuyQortInfoFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent('openBuyQortInfo', openBuyQortInfoFunc);
|
||||
};
|
||||
}, [openBuyQortInfoFunc]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Get QORT"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
width: "400px",
|
||||
maxWidth: '90vw',
|
||||
height: "400px",
|
||||
maxHeight: '90vh',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography>Get QORT using Qortal's crosschain trade portal</Typography>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
"&:hover": { backgroundColor: "secondary.main" },
|
||||
transition: "all 0.1s ease-in-out",
|
||||
padding: "5px",
|
||||
borderRadius: "5px",
|
||||
gap: "5px",
|
||||
}}
|
||||
onClick={async () => {
|
||||
executeEvent("addTab", {
|
||||
data: { service: "APP", name: "q-trade" },
|
||||
});
|
||||
executeEvent("open-apps-mode", {});
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
height: '30px'
|
||||
}}
|
||||
src={qTradeLogo}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
Trade QORT
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<Spacer height='40px' />
|
||||
<Typography sx={{
|
||||
textDecoration: 'underline'
|
||||
}}>Benefits of having QORT</Typography>
|
||||
<List
|
||||
sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
|
||||
aria-label="contacts"
|
||||
open={isOpen}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<ListItem disablePadding>
|
||||
|
||||
<ListItemIcon>
|
||||
<RadioButtonCheckedIcon sx={{
|
||||
color: 'white'
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Create transactions on the Qortal Blockchain" />
|
||||
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
|
||||
<ListItemIcon>
|
||||
<RadioButtonCheckedIcon sx={{
|
||||
color: 'white'
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Having at least 4 QORT in your balance allows you to send chat messages at near instant speed." />
|
||||
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
setIsOpen(false)
|
||||
<DialogTitle id="alert-dialog-title">{'Get QORT'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '10px',
|
||||
height: '350px',
|
||||
maxHeight: '80vh',
|
||||
maxWidth: '90vw',
|
||||
padding: '10px',
|
||||
width: '400px',
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
Get QORT using Qortal's crosschain trade portal
|
||||
</Typography>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
'&:hover': { backgroundColor: theme.palette.secondary.main },
|
||||
transition: 'all 0.1s ease-in-out',
|
||||
padding: '5px',
|
||||
borderRadius: '5px',
|
||||
gap: '5px',
|
||||
}}
|
||||
onClick={async () => {
|
||||
executeEvent('addTab', {
|
||||
data: { service: 'APP', name: 'q-trade' },
|
||||
});
|
||||
executeEvent('open-apps-mode', {});
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
height: '30px',
|
||||
}}
|
||||
src={qTradeLogo}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Trade QORT
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<Spacer height="15px" />
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
>
|
||||
Benefits of having QORT
|
||||
</Typography>
|
||||
<List
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
maxWidth: 360,
|
||||
width: '100%',
|
||||
}}
|
||||
aria-label="contacts"
|
||||
>
|
||||
<ListItem disablePadding>
|
||||
<ListItemIcon>
|
||||
<RadioButtonCheckedIcon
|
||||
sx={{
|
||||
color: theme.palette.primary.dark,
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Create transactions on the Qortal Blockchain" />
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemIcon>
|
||||
<RadioButtonCheckedIcon
|
||||
sx={{
|
||||
color: theme.palette.primary.dark,
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Having at least 4 QORT in your balance allows you to send chat messages at near instant speed." />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
@ -1,21 +1,7 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { GroupMail } from "../Group/Forum/GroupMail";
|
||||
import { MyContext, isMobile } from "../../App";
|
||||
import { getRootHeight } from "../../utils/mobile/mobileUtils";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { AdminSpaceInner } from "./AdminSpaceInner";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { AdminSpaceInner } from './AdminSpaceInner';
|
||||
|
||||
export const AdminSpace = ({
|
||||
selectedGroup,
|
||||
@ -26,11 +12,11 @@ export const AdminSpace = ({
|
||||
isAdmin,
|
||||
myAddress,
|
||||
hide,
|
||||
defaultThread,
|
||||
defaultThread,
|
||||
setDefaultThread,
|
||||
setIsForceShowCreationKeyPopup
|
||||
setIsForceShowCreationKeyPopup,
|
||||
}) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const [isMoved, setIsMoved] = useState(false);
|
||||
useEffect(() => {
|
||||
if (hide) {
|
||||
@ -42,26 +28,37 @@ export const AdminSpace = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
opacity: hide ? 0 : 1,
|
||||
visibility: hide && 'hidden',
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
left: hide && '-1000px'
|
||||
}}
|
||||
>
|
||||
{!isAdmin && <Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: '25px'
|
||||
}}><Typography>Sorry, this space is only for Admins.</Typography></Box>}
|
||||
{isAdmin && <AdminSpaceInner setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup} adminsWithNames={adminsWithNames} selectedGroup={selectedGroup} />}
|
||||
|
||||
</div>
|
||||
style={{
|
||||
// reference to change height
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isAdmin && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography>Sorry, this space is only for Admins.</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<AdminSpaceInner
|
||||
setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup}
|
||||
adminsWithNames={adminsWithNames}
|
||||
selectedGroup={selectedGroup}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,39 +1,42 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
} from "../../App";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
} from '../../App';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import {
|
||||
decryptResource,
|
||||
getPublishesFromAdmins,
|
||||
validateSecretKey,
|
||||
} from "../Group/Group";
|
||||
import { getFee } from "../../background";
|
||||
import { base64ToUint8Array } from "../../qdn/encryption/group-encryption";
|
||||
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption";
|
||||
import { formatTimestampForum } from "../../utils/time";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
} from '../Group/Group';
|
||||
import { getFee } from '../../background';
|
||||
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import { formatTimestampForum } from '../../utils/time';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
|
||||
export const getPublishesFromAdminsAdminSpace = async (
|
||||
admins: string[],
|
||||
groupId
|
||||
) => {
|
||||
const queryString = admins.map((name) => `name=${name}`).join("&");
|
||||
const queryString = admins.map((name) => `name=${name}`).join('&');
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=admins-symmetric-qchat-group-${groupId}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("network error");
|
||||
throw new Error('network error');
|
||||
}
|
||||
const adminData = await response.json();
|
||||
|
||||
const filterId = adminData.filter(
|
||||
(data: any) => data.identifier === `admins-symmetric-qchat-group-${groupId}`
|
||||
);
|
||||
|
||||
if (filterId?.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sortedData = filterId.sort((a: any, b: any) => {
|
||||
// Get the most recent date for both a and b
|
||||
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
|
||||
@ -87,10 +90,11 @@ export const AdminSpaceInner = ({
|
||||
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||
if (!validateSecretKey(decryptedKeyToObject))
|
||||
throw new Error("SecretKey is not valid");
|
||||
throw new Error('SecretKey is not valid');
|
||||
setAdminGroupSecretKey(decryptedKeyToObject);
|
||||
setAdminGroupSecretKeyPublishDetails(getLatestPublish);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsFetchingAdminGroupSecretKey(false);
|
||||
}
|
||||
@ -106,6 +110,7 @@ export const AdminSpaceInner = ({
|
||||
if (getLatestPublish === false) setGroupSecretKeyPublishDetails(false);
|
||||
setGroupSecretKeyPublishDetails(getLatestPublish);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsFetchingGroupSecretKey(false);
|
||||
}
|
||||
@ -113,15 +118,17 @@ export const AdminSpaceInner = ({
|
||||
|
||||
const createCommonSecretForAdmins = async () => {
|
||||
try {
|
||||
const fee = await getFee("ARBITRARY");
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
await show({
|
||||
message: "Would you like to perform an ARBITRARY transaction?",
|
||||
publishFee: fee.fee + " QORT",
|
||||
message: 'Would you like to perform an ARBITRARY transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
setIsLoadingPublishKey(true);
|
||||
|
||||
window
|
||||
.sendMessage("encryptAndPublishSymmetricKeyGroupChatForAdmins", {
|
||||
.sendMessage('encryptAndPublishSymmetricKeyGroupChatForAdmins', {
|
||||
groupId: selectedGroup,
|
||||
previousData: adminGroupSecretKey,
|
||||
admins: adminsWithNames,
|
||||
@ -129,27 +136,29 @@ export const AdminSpaceInner = ({
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnackCustom({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message:
|
||||
"Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.",
|
||||
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.',
|
||||
});
|
||||
setOpenSnackGlobal(true);
|
||||
return;
|
||||
}
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: response?.error || "unable to re-encrypt secret key",
|
||||
type: 'error',
|
||||
message: response?.error || 'unable to re-encrypt secret key',
|
||||
});
|
||||
setOpenSnackGlobal(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: error?.message || "unable to re-encrypt secret key",
|
||||
type: 'error',
|
||||
message: error?.message || 'unable to re-encrypt secret key',
|
||||
});
|
||||
setOpenSnackGlobal(true);
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -159,27 +168,32 @@ export const AdminSpaceInner = ({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: "10px",
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>Reminder: After publishing the key, it will take a couple of minutes for it to appear. Please just wait.</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Reminder: After publishing the key, it will take a couple of minutes for
|
||||
it to appear. Please just wait.
|
||||
</Typography>
|
||||
<Spacer height="25px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
width: "300px",
|
||||
maxWidth: "90%",
|
||||
padding: '10px',
|
||||
border: '1px solid gray',
|
||||
borderRadius: '6px'
|
||||
borderRadius: '6px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
maxWidth: '90%',
|
||||
padding: '10px',
|
||||
width: '300px',
|
||||
}}
|
||||
>
|
||||
{isFetchingGroupSecretKey && (
|
||||
@ -191,33 +205,47 @@ export const AdminSpaceInner = ({
|
||||
)}
|
||||
{groupSecretKeyPublishDetails && (
|
||||
<Typography>
|
||||
Last encryption date:{" "}
|
||||
Last encryption date:{' '}
|
||||
{formatTimestampForum(
|
||||
groupSecretKeyPublishDetails?.updated ||
|
||||
groupSecretKeyPublishDetails?.created
|
||||
)}{" "}
|
||||
)}{' '}
|
||||
{` by ${groupSecretKeyPublishDetails?.name}`}
|
||||
</Typography>
|
||||
)}
|
||||
<Button disabled={isFetchingGroupSecretKey} onClick={()=> setIsForceShowCreationKeyPopup(true)} variant="contained">
|
||||
<Button
|
||||
disabled={isFetchingGroupSecretKey}
|
||||
onClick={() => setIsForceShowCreationKeyPopup(true)}
|
||||
variant="contained"
|
||||
>
|
||||
Publish group secret key
|
||||
</Button>
|
||||
|
||||
<Spacer height="20px" />
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>This key is to encrypt GROUP related content. This is the only one used in this UI as of now. All group members will be able to see content encrypted with this key.</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
This key is to encrypt GROUP related content. This is the only one
|
||||
used in this UI as of now. All group members will be able to see
|
||||
content encrypted with this key.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
width: "300px",
|
||||
maxWidth: "90%",
|
||||
padding: '10px',
|
||||
border: '1px solid gray',
|
||||
borderRadius: '6px'
|
||||
borderRadius: '6px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
maxWidth: '90%',
|
||||
padding: '10px',
|
||||
width: '300px',
|
||||
}}
|
||||
>
|
||||
{isFetchingAdminGroupSecretKey && (
|
||||
@ -228,20 +256,31 @@ export const AdminSpaceInner = ({
|
||||
)}
|
||||
{adminGroupSecretKeyPublishDetails && (
|
||||
<Typography>
|
||||
Last encryption date:{" "}
|
||||
Last encryption date:{' '}
|
||||
{formatTimestampForum(
|
||||
adminGroupSecretKeyPublishDetails?.updated ||
|
||||
adminGroupSecretKeyPublishDetails?.created
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
<Button disabled={isFetchingAdminGroupSecretKey} onClick={createCommonSecretForAdmins} variant="contained">
|
||||
<Button
|
||||
disabled={isFetchingAdminGroupSecretKey}
|
||||
onClick={createCommonSecretForAdmins}
|
||||
variant="contained"
|
||||
>
|
||||
Publish admin secret key
|
||||
</Button>
|
||||
|
||||
<Spacer height="20px" />
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>This key is to encrypt ADMIN related content. Only admins would see content encrypted with it.</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
This key is to encrypt ADMIN related content. Only admins would see
|
||||
content encrypted with it.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
AuthenticatedContainerInnerTop,
|
||||
CustomButton,
|
||||
} from '../../styles/App-styles';
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import { Box, CircularProgress, useTheme } from '@mui/material';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
@ -39,6 +39,7 @@ export const AnnouncementDiscussion = ({
|
||||
myName,
|
||||
isPrivate,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isFocusedParent, setIsFocusedParent] = useState(false);
|
||||
@ -83,7 +84,9 @@ export const AnnouncementDiscussion = ({
|
||||
[`${identifier}-${name}`]: messageData,
|
||||
};
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const publishAnc = async ({ encryptedData, identifier }: any) => {
|
||||
@ -107,7 +110,9 @@ export const AnnouncementDiscussion = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const setTempData = async () => {
|
||||
@ -123,7 +128,9 @@ export const AnnouncementDiscussion = ({
|
||||
});
|
||||
setTempPublishedList(tempData);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const publishComment = async () => {
|
||||
@ -206,6 +213,7 @@ export const AnnouncementDiscussion = ({
|
||||
getData({ name: data.name, identifier: data.identifier }, isPrivate);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
@ -235,7 +243,9 @@ export const AnnouncementDiscussion = ({
|
||||
for (const data of responseData) {
|
||||
getData({ name: data.name, identifier: data.identifier }, isPrivate);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const combinedListTempAndReal = useMemo(() => {
|
||||
@ -266,19 +276,19 @@ export const AnnouncementDiscussion = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: isMobile ? '100%' : '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '100%' : '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AuthenticatedContainerInnerTop
|
||||
@ -293,6 +303,7 @@ export const AnnouncementDiscussion = ({
|
||||
}}
|
||||
/>
|
||||
</AuthenticatedContainerInnerTop>
|
||||
|
||||
<Spacer height="20px" />
|
||||
</div>
|
||||
<AnnouncementList
|
||||
@ -306,30 +317,27 @@ export const AnnouncementDiscussion = ({
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
maxHeight: isMobile ? 'auto' : '400px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
maxHeight: '400px',
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// height: '100%',
|
||||
flexGrow: isMobile && 1,
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
@ -345,11 +353,11 @@ export const AnnouncementDiscussion = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100&',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100&',
|
||||
}}
|
||||
>
|
||||
{isFocusedParent && (
|
||||
@ -361,13 +369,13 @@ export const AnnouncementDiscussion = ({
|
||||
// Unfocus the editor
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: 'red',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
background: 'red',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{` Close`}
|
||||
@ -379,25 +387,25 @@ export const AnnouncementDiscussion = ({
|
||||
publishComment();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: theme.palette.background.default,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,173 +1,206 @@
|
||||
import { Message } from "@chatscope/chat-ui-kit-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { MessageDisplay } from "./MessageDisplay";
|
||||
import { Avatar, Box, Typography } from "@mui/material";
|
||||
import { formatTimestamp } from "../../utils/time";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { MessageDisplay } from './MessageDisplay';
|
||||
import { Avatar, Box, Typography, useTheme } from '@mui/material';
|
||||
import { formatTimestamp } from '../../utils/time';
|
||||
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import { getBaseApi } from "../../background";
|
||||
import { requestQueueCommentCount } from "./GroupAnnouncements";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { getArbitraryEndpointReact, getBaseApiReact } from "../../App";
|
||||
import { WrapperUserAction } from "../WrapperUserAction";
|
||||
export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment, myName }) => {
|
||||
import { getBaseApi } from '../../background';
|
||||
import { requestQueueCommentCount } from './GroupAnnouncements';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { getArbitraryEndpointReact, getBaseApiReact } from '../../App';
|
||||
import { WrapperUserAction } from '../WrapperUserAction';
|
||||
|
||||
const [commentLength, setCommentLength] = useState(0)
|
||||
const getNumberOfComments = React.useCallback(
|
||||
async () => {
|
||||
try {
|
||||
const offset = 0;
|
||||
export const AnnouncementItem = ({
|
||||
message,
|
||||
messageData,
|
||||
setSelectedAnnouncement,
|
||||
disableComment,
|
||||
myName,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [commentLength, setCommentLength] = useState(0);
|
||||
const getNumberOfComments = React.useCallback(async () => {
|
||||
try {
|
||||
const offset = 0;
|
||||
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const identifier = `cm-${message.identifier}`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
|
||||
|
||||
const response = await requestQueueCommentCount.enqueue(() => {
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
})
|
||||
const responseData = await response.json();
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const identifier = `cm-${message.identifier}`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
|
||||
|
||||
const response = await requestQueueCommentCount.enqueue(() => {
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
const responseData = await response.json();
|
||||
|
||||
setCommentLength(responseData?.length);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (disableComment) return;
|
||||
getNumberOfComments();
|
||||
}, []);
|
||||
|
||||
setCommentLength(responseData?.length);
|
||||
|
||||
} catch (error) {
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
useEffect(()=> {
|
||||
if(disableComment) return
|
||||
getNumberOfComments()
|
||||
}, [])
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "10px",
|
||||
backgroundColor: "#232428",
|
||||
borderRadius: "7px",
|
||||
width: "95%",
|
||||
display: "flex",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: '7px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '7px',
|
||||
flexDirection: 'column'
|
||||
padding: '10px',
|
||||
width: '95%',
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: '#27282c',
|
||||
color: 'white'
|
||||
}}
|
||||
alt={message?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "7px",
|
||||
width: '100%'
|
||||
display: 'flex',
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
|
||||
<Typography
|
||||
<WrapperUserAction
|
||||
disabled={myName === message?.name}
|
||||
address={undefined}
|
||||
name={message?.name}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
alt={message?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
<Box
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{message?.name}
|
||||
</Typography>
|
||||
</WrapperUserAction>
|
||||
{!messageData?.decryptedData && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{messageData?.decryptedData?.message && (
|
||||
<>
|
||||
{messageData?.type === "notification" ? (
|
||||
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
|
||||
) : (
|
||||
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<WrapperUserAction
|
||||
disabled={myName === message?.name}
|
||||
address={undefined}
|
||||
name={message?.name}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: 'Inter',
|
||||
color: 'cadetBlue',
|
||||
}}
|
||||
>
|
||||
{message?.name}
|
||||
</Typography>
|
||||
</WrapperUserAction>
|
||||
{!messageData?.decryptedData && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{messageData?.decryptedData?.message && (
|
||||
<>
|
||||
{messageData?.type === 'notification' ? (
|
||||
<MessageDisplay
|
||||
htmlContent={messageData?.decryptedData?.message}
|
||||
/>
|
||||
) : (
|
||||
<MessageDisplay
|
||||
htmlContent={messageData?.decryptedData?.message}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Typography sx={{
|
||||
fontSize: '14px',
|
||||
color: 'gray',
|
||||
fontFamily: 'Inter'
|
||||
}}>{formatTimestamp(message.created)}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
{formatTimestamp(message.created)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{!disableComment && (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '20px',
|
||||
cursor: 'pointer',
|
||||
opacity: 0.4,
|
||||
borderTop: '1px solid white',
|
||||
|
||||
}} onClick={()=> setSelectedAnnouncement(message)}>
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
gap: '25px',
|
||||
alignItems: 'center',
|
||||
|
||||
}}>
|
||||
<ChatBubbleIcon sx={{
|
||||
fontSize: '20px'
|
||||
}} />
|
||||
{commentLength ? (
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
|
||||
) : (
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>Leave comment</Typography>
|
||||
)}
|
||||
|
||||
{!disableComment && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
borderTop: '1px solid white',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
opacity: 0.4,
|
||||
padding: '20px',
|
||||
width: '100%',
|
||||
}}
|
||||
onClick={() => setSelectedAnnouncement(message)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ChatBubbleIcon
|
||||
sx={{
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
{commentLength ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
|
||||
) : (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Leave comment
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<ArrowForwardIosIcon
|
||||
sx={{
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<ArrowForwardIosIcon sx={{
|
||||
fontSize: '20px'
|
||||
}} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,5 @@
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
List,
|
||||
AutoSizer,
|
||||
CellMeasurerCache,
|
||||
CellMeasurer,
|
||||
} from 'react-virtualized';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { CellMeasurerCache } from 'react-virtualized';
|
||||
import { AnnouncementItem } from './AnnouncementItem';
|
||||
import { Box } from '@mui/material';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
@ -37,12 +32,12 @@ export const AnnouncementList = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
flexGrow: 1,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
@ -57,11 +52,11 @@ export const AnnouncementList = ({
|
||||
<div
|
||||
key={message?.identifier}
|
||||
style={{
|
||||
marginBottom: '10px',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginBottom: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AnnouncementItem
|
||||
@ -89,10 +84,10 @@ export const AnnouncementList = ({
|
||||
</AutoSizer> */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginTop: '25px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{showLoadMore && (
|
||||
|
@ -1,16 +1,14 @@
|
||||
import React, { useState } from "react";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import {
|
||||
MainContainer,
|
||||
ChatContainer,
|
||||
MessageList,
|
||||
Message,
|
||||
MessageInput,
|
||||
Avatar
|
||||
} from "@chatscope/chat-ui-kit-react";
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
Avatar,
|
||||
} from '@chatscope/chat-ui-kit-react';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
|
||||
export const ChatContainerComp = ({messages}) => {
|
||||
export const ChatContainerComp = ({ messages }) => {
|
||||
// const [messages, setMessages] = useState([
|
||||
// { id: 1, text: "Hello! How are you?", sender: "Joe"},
|
||||
// { id: 2, text: "I'm good, thank you!", sender: "Me" }
|
||||
@ -26,31 +24,31 @@ export const ChatContainerComp = ({messages}) => {
|
||||
// };
|
||||
|
||||
return (
|
||||
<div style={{ height: "500px", width: "300px" }}>
|
||||
<div style={{ height: '500px', width: '300px' }}>
|
||||
<MainContainer>
|
||||
<ChatContainer>
|
||||
<MessageList>
|
||||
{messages.map((msg) => (
|
||||
<Message
|
||||
key={msg.id}
|
||||
model={{
|
||||
message: msg.text,
|
||||
sentTime: "just now",
|
||||
sender: msg.senderName,
|
||||
direction: 'incoming',
|
||||
position: "single"
|
||||
}}
|
||||
>
|
||||
{msg.direction === "incoming" && <Avatar name={msg.senderName} />}
|
||||
</Message>
|
||||
))}
|
||||
</MessageList>
|
||||
|
||||
<MessageList>
|
||||
{messages.map((msg) => (
|
||||
<Message
|
||||
key={msg.id}
|
||||
model={{
|
||||
message: msg.text,
|
||||
sentTime: 'just now',
|
||||
sender: msg.senderName,
|
||||
direction: 'incoming',
|
||||
position: 'single',
|
||||
}}
|
||||
>
|
||||
{msg.direction === 'incoming' && (
|
||||
<Avatar name={msg.senderName} />
|
||||
)}
|
||||
</Message>
|
||||
))}
|
||||
</MessageList>
|
||||
|
||||
<MessageInput placeholder="Type a message..." />
|
||||
</ChatContainer>
|
||||
</MainContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
@ -7,13 +7,12 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import { ChatList } from './ChatList';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
import Tiptap from './TipTap';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { Box, ButtonBase, Input, Typography } from '@mui/material';
|
||||
import { Box, ButtonBase, Input, Typography, useTheme } from '@mui/material';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import { getNameInfo } from '../Group/Group';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
@ -36,7 +35,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
|
||||
import { ExitIcon } from '../../assets/Icons/ExitIcon';
|
||||
import { MessageItem, ReplyPreview } from './MessageItem';
|
||||
import { ReplyPreview } from './MessageItem';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
||||
@ -52,6 +51,7 @@ export const ChatDirect = ({
|
||||
close,
|
||||
setMobileViewModeKeepOpen,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
|
||||
const [isFocusedParent, setIsFocusedParent] = useState(false);
|
||||
const [onEditMessage, setOnEditMessage] = useState(null);
|
||||
@ -87,7 +87,9 @@ export const ChatDirect = ({
|
||||
const publicKey = await getPublicKey(address);
|
||||
if (publicKeyOfRecipientRef.current !== selectedDirect?.address) return;
|
||||
setPublicKeyOfRecipient(publicKey);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const tempMessages = useMemo(() => {
|
||||
@ -167,6 +169,7 @@ export const ChatDirect = ({
|
||||
text: item.message,
|
||||
unread: item?.sender === myAddress ? false : true,
|
||||
}));
|
||||
|
||||
setMessages((prev) => [...prev, ...formatted]);
|
||||
setChatReferences((prev) => {
|
||||
const organizedChatReferences = { ...prev };
|
||||
@ -183,7 +186,9 @@ export const ChatDirect = ({
|
||||
{}),
|
||||
edit: item,
|
||||
};
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
return organizedChatReferences;
|
||||
});
|
||||
@ -214,7 +219,9 @@ export const ChatDirect = ({
|
||||
{}),
|
||||
edit: item,
|
||||
};
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
return organizedChatReferences;
|
||||
});
|
||||
@ -227,7 +234,9 @@ export const ChatDirect = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const forceCloseWebSocket = () => {
|
||||
@ -368,7 +377,6 @@ export const ChatDirect = ({
|
||||
senderName: myName,
|
||||
});
|
||||
setNewChat(null);
|
||||
|
||||
window
|
||||
.sendMessage('addTimestampEnterChat', {
|
||||
timestamp: Date.now(),
|
||||
@ -396,7 +404,6 @@ export const ChatDirect = ({
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
const clearEditorContent = () => {
|
||||
@ -537,39 +544,39 @@ export const ChatDirect = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: isMobile ? '100%' : '100vh',
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '100%' : '100vh',
|
||||
width: '100%',
|
||||
background: !isMobile && 'var(--bg-2)',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
onClick={close}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
alignSelf: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '3px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
margin: '10px 0px',
|
||||
padding: '4px 6px',
|
||||
width: 'fit-content',
|
||||
borderRadius: '3px',
|
||||
background: 'rgb(35, 36, 40)',
|
||||
margin: '10px 0px',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontSize: isMobile ? '20px' : '20px',
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontSize: isMobile ? '14px' : '14px',
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Close Direct Chat
|
||||
@ -579,26 +586,26 @@ export const ChatDirect = ({
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
marginTop: '14px',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
height: '15px',
|
||||
justifyContent: 'center',
|
||||
marginTop: '14px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '320px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
@ -623,10 +630,10 @@ export const ChatDirect = ({
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '50px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -670,42 +677,40 @@ export const ChatDirect = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
overflow: !isMobile && 'auto',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
width: 'calc(100% - 100px)',
|
||||
justifyContent: 'flex-end',
|
||||
overflow: 'auto',
|
||||
width: 'calc(100% - 100px)',
|
||||
}}
|
||||
>
|
||||
{replyMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: 'calc(100% - 100px)',
|
||||
justifyContent: 'flex-end',
|
||||
width: 'calc(100% - 100px)',
|
||||
}}
|
||||
>
|
||||
<ReplyPreview message={replyMessage} />
|
||||
@ -723,9 +728,9 @@ export const ChatDirect = ({
|
||||
{onEditMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@ -735,7 +740,6 @@ export const ChatDirect = ({
|
||||
onClick={() => {
|
||||
setReplyMessage(null);
|
||||
setOnEditMessage(null);
|
||||
|
||||
clearEditorContent();
|
||||
}}
|
||||
>
|
||||
@ -756,9 +760,9 @@ export const ChatDirect = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -774,12 +778,11 @@ export const ChatDirect = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100px',
|
||||
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100px',
|
||||
}}
|
||||
>
|
||||
<CustomButton
|
||||
@ -788,26 +791,28 @@ export const ChatDirect = ({
|
||||
sendMessage();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: isSending
|
||||
? theme.palette.background.default
|
||||
: theme.palette.background.paper,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
marginTop: 'auto',
|
||||
minWidth: 'auto',
|
||||
padding: '5px',
|
||||
width: '100px',
|
||||
minWidth: 'auto',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -815,12 +820,14 @@ export const ChatDirect = ({
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: 'Loading chat... please wait.',
|
||||
}}
|
||||
/>
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
|
@ -7,15 +7,10 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CreateCommonSecret } from './CreateCommonSecret';
|
||||
import { reusableGet } from '../../qdn/publish/pubish';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
decodeBase64ForUIChatMessages,
|
||||
objectToBase64,
|
||||
} from '../../qdn/encryption/group-encryption';
|
||||
import { ChatContainerComp } from './ChatContainer';
|
||||
import { ChatList } from './ChatList';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
import Tiptap from './TipTap';
|
||||
@ -38,7 +33,7 @@ import {
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../utils/events';
|
||||
import { Box, ButtonBase, Divider, Typography } from '@mui/material';
|
||||
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { ReplyPreview } from './MessageItem';
|
||||
import { ExitIcon } from '../../assets/Icons/ExitIcon';
|
||||
@ -128,7 +123,9 @@ export const ChatGroup = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -192,7 +189,9 @@ export const ChatGroup = ({
|
||||
handleSecretKeyCreationInProgress();
|
||||
return;
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -578,7 +577,9 @@ export const ChatGroup = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const forceCloseWebSocket = () => {
|
||||
@ -621,7 +622,9 @@ export const ChatGroup = ({
|
||||
middletierFunc(JSON.parse(e.data), selectedGroup);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
socketRef.current.onclose = () => {
|
||||
clearTimeout(groupSocketTimeoutRef.current);
|
||||
@ -700,7 +703,9 @@ export const ChatGroup = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const sendChatGroup = async ({
|
||||
@ -991,16 +996,18 @@ export const ChatGroup = ({
|
||||
setIsOpenQManager(true);
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: isMobile ? '100%' : '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
left: hide && '-100000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'absolute' : 'relative',
|
||||
left: hide && '-100000px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ChatList
|
||||
@ -1025,40 +1032,38 @@ export const ChatGroup = ({
|
||||
{(!!secretKey || isPrivate === false) && (
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
overflow: !isMobile && 'auto',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
width: 'calc(100% - 100px)',
|
||||
justifyContent: 'flex-end',
|
||||
overflow: 'auto',
|
||||
width: 'calc(100% - 100px)',
|
||||
}}
|
||||
>
|
||||
{replyMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@ -1078,9 +1083,9 @@ export const ChatGroup = ({
|
||||
{onEditMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@ -1113,9 +1118,9 @@ export const ChatGroup = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -1131,11 +1136,11 @@ export const ChatGroup = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100px',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100px',
|
||||
}}
|
||||
>
|
||||
<CustomButton
|
||||
@ -1144,26 +1149,28 @@ export const ChatGroup = ({
|
||||
sendMessage();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: isSending
|
||||
? theme.palette.background.default
|
||||
: theme.palette.background.paper,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
marginTop: 'auto',
|
||||
minWidth: 'auto',
|
||||
padding: '5px',
|
||||
width: '100px',
|
||||
minWidth: 'auto',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -1175,25 +1182,24 @@ export const ChatGroup = ({
|
||||
{isOpenQManager !== null && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
height: '600px',
|
||||
|
||||
maxHeight: '100vh',
|
||||
width: '400px',
|
||||
maxWidth: '100vw',
|
||||
backgroundColor: '#27282c',
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
bottom: 0,
|
||||
boxShadow: 4,
|
||||
display: hideView
|
||||
? 'none'
|
||||
: isOpenQManager === true
|
||||
? 'block'
|
||||
: 'none',
|
||||
boxShadow: 4,
|
||||
height: '600px',
|
||||
maxHeight: '100vh',
|
||||
maxWidth: '100vw',
|
||||
overflow: 'hidden',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
width: '400px',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -1204,12 +1210,11 @@ export const ChatGroup = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
justifyContent: 'space-between',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Manager</Typography>
|
||||
@ -1241,12 +1246,14 @@ export const ChatGroup = ({
|
||||
)}
|
||||
|
||||
{/* <ChatContainerComp messages={formatMessages} /> */}
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: 'Loading chat... please wait.',
|
||||
}}
|
||||
/>
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
|
@ -1,23 +1,15 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { MessageItem } from "./MessageItem";
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { ChatOptions } from "./ChatOptions";
|
||||
import ErrorBoundary from "../../common/ErrorBoundary";
|
||||
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { MessageItem } from './MessageItem';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { ChatOptions } from './ChatOptions';
|
||||
import ErrorBoundary from '../../common/ErrorBoundary';
|
||||
|
||||
export const ChatList = ({
|
||||
initialMessages,
|
||||
myAddress,
|
||||
tempMessages,
|
||||
chatId,
|
||||
onReply,
|
||||
onEdit,
|
||||
handleReaction,
|
||||
@ -29,7 +21,7 @@ export const ChatList = ({
|
||||
enableMentions,
|
||||
openQManager,
|
||||
hasSecretKey,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
}) => {
|
||||
const parentRef = useRef();
|
||||
const [messages, setMessages] = useState(initialMessages);
|
||||
@ -42,33 +34,32 @@ export const ChatList = ({
|
||||
// Initialize the virtualizer
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: messages.length,
|
||||
getItemKey: (index) => messages[index]?.tempSignature || messages[index].signature,
|
||||
getItemKey: (index) =>
|
||||
messages[index]?.tempSignature || messages[index].signature,
|
||||
getScrollElement: () => parentRef?.current,
|
||||
estimateSize: useCallback(() => 80, []), // Provide an estimated height of items, adjust this as needed
|
||||
overscan: 10, // Number of items to render outside the visible area to improve smoothness
|
||||
});
|
||||
|
||||
const isAtBottom = useMemo(()=> {
|
||||
const isAtBottom = useMemo(() => {
|
||||
if (parentRef.current && rowVirtualizer?.isScrolling !== undefined) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
|
||||
const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed
|
||||
return atBottom
|
||||
}
|
||||
const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed
|
||||
return atBottom;
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
}, [rowVirtualizer?.isScrolling])
|
||||
return false;
|
||||
}, [rowVirtualizer?.isScrolling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!parentRef.current || rowVirtualizer?.isScrolling === undefined) return;
|
||||
if(isAtBottom){
|
||||
if (isAtBottom) {
|
||||
if (scrollingIntervalRef.current) {
|
||||
clearTimeout(scrollingIntervalRef.current);
|
||||
}
|
||||
setShowScrollDownButton(false);
|
||||
return;
|
||||
} else
|
||||
if (rowVirtualizer?.isScrolling) {
|
||||
} else if (rowVirtualizer?.isScrolling) {
|
||||
if (scrollingIntervalRef.current) {
|
||||
clearTimeout(scrollingIntervalRef.current);
|
||||
}
|
||||
@ -108,7 +99,13 @@ export const ChatList = ({
|
||||
|
||||
setTimeout(() => {
|
||||
const hasUnreadMessages = totalMessages.some(
|
||||
(msg) => msg.unread && !msg?.chatReference && !msg?.isTemp && (!msg?.chatReference && msg?.timestamp > lastSeenUnreadMessageTimestamp.current || 0)
|
||||
(msg) =>
|
||||
msg.unread &&
|
||||
!msg?.chatReference &&
|
||||
!msg?.isTemp &&
|
||||
((!msg?.chatReference &&
|
||||
msg?.timestamp > lastSeenUnreadMessageTimestamp.current) ||
|
||||
0)
|
||||
);
|
||||
if (parentRef.current) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
|
||||
@ -136,9 +133,9 @@ export const ChatList = ({
|
||||
const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1;
|
||||
if (rowVirtualizer) {
|
||||
if (divideIndex) {
|
||||
rowVirtualizer.scrollToIndex(divideIndex, { align: "start" });
|
||||
rowVirtualizer.scrollToIndex(divideIndex, { align: 'start' });
|
||||
} else {
|
||||
rowVirtualizer.scrollToIndex(index, { align: "end" });
|
||||
rowVirtualizer.scrollToIndex(index, { align: 'end' });
|
||||
}
|
||||
}
|
||||
handleMessageSeen();
|
||||
@ -152,7 +149,7 @@ export const ChatList = ({
|
||||
}))
|
||||
);
|
||||
setShowScrollButton(false);
|
||||
lastSeenUnreadMessageTimestamp.current = Date.now()
|
||||
lastSeenUnreadMessageTimestamp.current = Date.now();
|
||||
}, []);
|
||||
|
||||
const sentNewMessageGroupFunc = useCallback(() => {
|
||||
@ -166,9 +163,9 @@ export const ChatList = ({
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("sent-new-message-group", sentNewMessageGroupFunc);
|
||||
subscribeToEvent('sent-new-message-group', sentNewMessageGroupFunc);
|
||||
return () => {
|
||||
unsubscribeFromEvent("sent-new-message-group", sentNewMessageGroupFunc);
|
||||
unsubscribeFromEvent('sent-new-message-group', sentNewMessageGroupFunc);
|
||||
};
|
||||
}, [sentNewMessageGroupFunc]);
|
||||
|
||||
@ -181,21 +178,24 @@ export const ChatList = ({
|
||||
const goToMessage = useCallback((idx) => {
|
||||
rowVirtualizer.scrollToIndex(idx);
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@ -203,27 +203,26 @@ export const ChatList = ({
|
||||
className="List"
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
height: "0px",
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
height: '0px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: rowVirtualizer.getTotalSize(),
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const index = virtualRow.index;
|
||||
let message = messages[index] || null; // Safeguard against undefined
|
||||
@ -231,7 +230,7 @@ export const ChatList = ({
|
||||
let reply = null;
|
||||
let reactions = null;
|
||||
let isUpdating = false;
|
||||
|
||||
|
||||
try {
|
||||
// Safeguard for message existence
|
||||
if (message) {
|
||||
@ -239,16 +238,19 @@ export const ChatList = ({
|
||||
replyIndex = messages.findIndex(
|
||||
(msg) => msg?.signature === message?.repliedTo
|
||||
);
|
||||
|
||||
|
||||
if (message?.repliedTo && replyIndex !== -1) {
|
||||
reply = { ...(messages[replyIndex] || {}) };
|
||||
if (chatReferences?.[reply?.signature]?.edit) {
|
||||
reply.decryptedData = chatReferences[reply?.signature]?.edit;
|
||||
reply.text = chatReferences[reply?.signature]?.edit?.message;
|
||||
reply.editTimestamp = chatReferences[reply?.signature]?.edit?.timestamp
|
||||
reply.decryptedData =
|
||||
chatReferences[reply?.signature]?.edit;
|
||||
reply.text =
|
||||
chatReferences[reply?.signature]?.edit?.message;
|
||||
reply.editTimestamp =
|
||||
chatReferences[reply?.signature]?.edit?.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GroupDirectId logic
|
||||
if (message?.message && message?.groupDirectId) {
|
||||
replyIndex = messages.findIndex(
|
||||
@ -264,24 +266,34 @@ export const ChatList = ({
|
||||
status: message?.status,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Check for reactions and edits
|
||||
if (chatReferences?.[message.signature]) {
|
||||
reactions = chatReferences[message.signature]?.reactions || null;
|
||||
|
||||
if (chatReferences[message.signature]?.edit?.message && message?.text) {
|
||||
message.text = chatReferences[message.signature]?.edit?.message;
|
||||
message.isEdit = true
|
||||
message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
|
||||
reactions =
|
||||
chatReferences[message.signature]?.reactions || null;
|
||||
|
||||
if (
|
||||
chatReferences[message.signature]?.edit?.message &&
|
||||
message?.text
|
||||
) {
|
||||
message.text =
|
||||
chatReferences[message.signature]?.edit?.message;
|
||||
message.isEdit = true;
|
||||
message.editTimestamp =
|
||||
chatReferences[message.signature]?.edit?.timestamp;
|
||||
}
|
||||
if (chatReferences[message.signature]?.edit?.messageText && message?.messageText) {
|
||||
message.messageText = chatReferences[message.signature]?.edit?.messageText;
|
||||
message.isEdit = true
|
||||
message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
|
||||
if (
|
||||
chatReferences[message.signature]?.edit?.messageText &&
|
||||
message?.messageText
|
||||
) {
|
||||
message.messageText =
|
||||
chatReferences[message.signature]?.edit?.messageText;
|
||||
message.isEdit = true;
|
||||
message.editTimestamp =
|
||||
chatReferences[message.signature]?.edit?.timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Check if message is updating
|
||||
if (
|
||||
tempChatReferences?.some(
|
||||
@ -292,34 +304,37 @@ export const ChatList = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing message:", error, { index, message });
|
||||
console.error('Error processing message:', error, {
|
||||
index,
|
||||
message,
|
||||
});
|
||||
// Gracefully handle the error by providing fallback data
|
||||
message = null;
|
||||
reply = null;
|
||||
reactions = null;
|
||||
}
|
||||
// Render fallback if message is null
|
||||
// Render fallback if message is null
|
||||
if (!message) {
|
||||
return (
|
||||
<div
|
||||
key={virtualRow.index}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: "50%",
|
||||
transform: `translateY(${virtualRow.start}px) translateX(-50%)`,
|
||||
width: "100%",
|
||||
padding: "10px 0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography>Error loading message.</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={virtualRow.index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '50%',
|
||||
transform: `translateY(${virtualRow.start}px) translateX(-50%)`,
|
||||
width: '100%',
|
||||
padding: '10px 0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Typography>Error loading message.</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -327,49 +342,47 @@ export const ChatList = ({
|
||||
ref={rowVirtualizer.measureElement} //measure dynamic row height
|
||||
key={message.signature}
|
||||
style={{
|
||||
position: "absolute",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
left: '50%', // Move to the center horizontally
|
||||
overscrollBehavior: 'none',
|
||||
padding: '10px 0',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: "50%", // Move to the center horizontally
|
||||
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
|
||||
width: "100%", // Control width (90% of the parent)
|
||||
padding: "10px 0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
overscrollBehavior: "none",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
width: '100%', // Control width (90% of the parent)
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<Typography>
|
||||
Error loading content: Invalid Data
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<MessageItem
|
||||
isLast={index === messages.length - 1}
|
||||
lastSignature={lastSignature}
|
||||
message={message}
|
||||
onSeen={handleMessageSeen}
|
||||
isTemp={!!message?.isTemp}
|
||||
myAddress={myAddress}
|
||||
onReply={onReply}
|
||||
onEdit={onEdit}
|
||||
reply={reply}
|
||||
replyIndex={replyIndex}
|
||||
scrollToItem={goToMessage}
|
||||
handleReaction={handleReaction}
|
||||
reactions={reactions}
|
||||
isUpdating={isUpdating}
|
||||
isPrivate={isPrivate}
|
||||
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<MessageItem
|
||||
isLast={index === messages.length - 1}
|
||||
lastSignature={lastSignature}
|
||||
message={message}
|
||||
onSeen={handleMessageSeen}
|
||||
isTemp={!!message?.isTemp}
|
||||
myAddress={myAddress}
|
||||
onReply={onReply}
|
||||
onEdit={onEdit}
|
||||
reply={reply}
|
||||
replyIndex={replyIndex}
|
||||
scrollToItem={goToMessage}
|
||||
handleReaction={handleReaction}
|
||||
reactions={reactions}
|
||||
isUpdating={isUpdating}
|
||||
isPrivate={isPrivate}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -377,17 +390,17 @@ export const ChatList = ({
|
||||
<button
|
||||
onClick={() => scrollToBottom()}
|
||||
style={{
|
||||
backgroundColor: 'var(--unread)',
|
||||
border: 'none',
|
||||
borderRadius: '20px',
|
||||
bottom: 20,
|
||||
position: "absolute",
|
||||
color: theme.palette.text.primary,
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
padding: '10px 20px',
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
backgroundColor: "var(--unread)",
|
||||
color: "black",
|
||||
padding: "10px 20px",
|
||||
borderRadius: "20px",
|
||||
cursor: "pointer",
|
||||
zIndex: 10,
|
||||
border: "none",
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
Scroll to Unread Messages
|
||||
@ -397,18 +410,18 @@ export const ChatList = ({
|
||||
<button
|
||||
onClick={() => scrollToBottom()}
|
||||
style={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: 'none',
|
||||
borderRadius: '20px',
|
||||
bottom: 20,
|
||||
position: "absolute",
|
||||
color: theme.palette.text.primary,
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
outline: 'none',
|
||||
padding: '10px 20px',
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
backgroundColor: "var(--Mail-Background)",
|
||||
color: "white",
|
||||
padding: "10px 20px",
|
||||
borderRadius: "20px",
|
||||
cursor: "pointer",
|
||||
zIndex: 10,
|
||||
border: "none",
|
||||
outline: "none",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
Scroll to bottom
|
||||
@ -417,7 +430,7 @@ export const ChatList = ({
|
||||
</div>
|
||||
{enableMentions && (hasSecretKey || isPrivate === false) && (
|
||||
<ChatOptions
|
||||
openQManager={openQManager}
|
||||
openQManager={openQManager}
|
||||
messages={messages}
|
||||
goToMessage={goToMessage}
|
||||
members={members}
|
||||
|
@ -1,35 +1,56 @@
|
||||
import { Box, Button, Typography } from '@mui/material'
|
||||
import React, { useContext } from 'react'
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Button, Typography, useTheme } from '@mui/material';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { MyContext, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues } from '../../App';
|
||||
import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
pauseAllQueues,
|
||||
} from '../../App';
|
||||
import { getFee } from '../../background';
|
||||
import { decryptResource, getGroupAdmins, validateSecretKey } from '../Group/Group';
|
||||
import {
|
||||
decryptResource,
|
||||
getGroupAdmins,
|
||||
validateSecretKey,
|
||||
} from '../Group/Group';
|
||||
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
|
||||
export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, secretKeyDetails, userInfo, noSecretKey, setHideCommonKeyPopup, setIsForceShowCreationKeyPopup, isForceShowCreationKeyPopup}) => {
|
||||
export const CreateCommonSecret = ({
|
||||
groupId,
|
||||
secretKey,
|
||||
isOwner,
|
||||
myAddress,
|
||||
secretKeyDetails,
|
||||
userInfo,
|
||||
noSecretKey,
|
||||
setHideCommonKeyPopup,
|
||||
setIsForceShowCreationKeyPopup,
|
||||
isForceShowCreationKeyPopup,
|
||||
}) => {
|
||||
const { show, setTxList } = useContext(MyContext);
|
||||
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const getPublishesFromAdmins = async (admins: string[]) => {
|
||||
// const validApi = await findUsableApi();
|
||||
const queryString = admins.map((name) => `name=${name}`).join("&");
|
||||
const queryString = admins.map((name) => `name=${name}`).join('&');
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
|
||||
groupId
|
||||
}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
|
||||
const response = await fetch(url);
|
||||
if(!response.ok){
|
||||
throw new Error('network error')
|
||||
if (!response.ok) {
|
||||
throw new Error('network error');
|
||||
}
|
||||
const adminData = await response.json();
|
||||
|
||||
|
||||
const filterId = adminData.filter(
|
||||
(data: any) =>
|
||||
data.identifier === `symmetric-qchat-group-${groupId}`
|
||||
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
|
||||
);
|
||||
if (filterId?.length === 0) {
|
||||
return false;
|
||||
@ -38,149 +59,182 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
|
||||
// Get the most recent date for both a and b
|
||||
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
|
||||
const dateB = b.updated ? new Date(b.updated) : new Date(b.created);
|
||||
|
||||
|
||||
// Sort by most recent
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
|
||||
|
||||
return sortedData[0];
|
||||
};
|
||||
const getSecretKey = async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => {
|
||||
|
||||
const getSecretKey = async (
|
||||
loadingGroupParam?: boolean,
|
||||
secretKeyToPublish?: boolean
|
||||
) => {
|
||||
try {
|
||||
pauseAllQueues()
|
||||
|
||||
|
||||
|
||||
const {names} = await getGroupAdmins(groupId);
|
||||
if(!names.length){
|
||||
throw new Error('Network error')
|
||||
pauseAllQueues();
|
||||
|
||||
const { names } = await getGroupAdmins(groupId);
|
||||
if (!names.length) {
|
||||
throw new Error('Network error');
|
||||
}
|
||||
const publish = await getPublishesFromAdmins(names);
|
||||
|
||||
|
||||
|
||||
if (publish === false) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const res = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
||||
publish.identifier
|
||||
}?encoding=base64&rebuild=true`
|
||||
);
|
||||
const data = await res.text();
|
||||
|
||||
|
||||
const decryptedKey: any = await decryptResource(data);
|
||||
|
||||
|
||||
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||
|
||||
|
||||
if (!validateSecretKey(decryptedKeyToObject))
|
||||
throw new Error("SecretKey is not valid");
|
||||
|
||||
throw new Error('SecretKey is not valid');
|
||||
|
||||
if (decryptedKeyToObject) {
|
||||
|
||||
return decryptedKeyToObject;
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
|
||||
} finally {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const createCommonSecret = async ()=> {
|
||||
try {
|
||||
const fee = await getFee('ARBITRARY')
|
||||
await show({
|
||||
message: "Would you like to perform an ARBITRARY transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
setIsLoading(true)
|
||||
const createCommonSecret = async () => {
|
||||
try {
|
||||
const fee = await getFee('ARBITRARY');
|
||||
await show({
|
||||
message: 'Would you like to perform an ARBITRARY transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoading(true);
|
||||
|
||||
const secretKey2 = await getSecretKey()
|
||||
if((!secretKey2 && secretKey2 !== false)) throw new Error('invalid secret key')
|
||||
if (secretKey2 && !validateSecretKey(secretKey2)) throw new Error('invalid secret key')
|
||||
const secretKey2 = await getSecretKey();
|
||||
if (!secretKey2 && secretKey2 !== false)
|
||||
throw new Error('invalid secret key');
|
||||
if (secretKey2 && !validateSecretKey(secretKey2))
|
||||
throw new Error('invalid secret key');
|
||||
|
||||
const secretKeyToSend = !secretKey2 ? null : secretKey2
|
||||
|
||||
|
||||
window.sendMessage("encryptAndPublishSymmetricKeyGroupChat", {
|
||||
groupId: groupId,
|
||||
previousData: secretKeyToSend,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-common-secret',
|
||||
label: `Published secret key for group ${groupId}: awaiting confirmation`,
|
||||
labelDone: `Published secret key for group ${groupId}: success!`,
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsForceShowCreationKeyPopup(false)
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to encrypt and publish symmetric key for group chat:", error.message || "An error occurred");
|
||||
setIsLoading(false);
|
||||
const secretKeyToSend = !secretKey2 ? null : secretKey2;
|
||||
|
||||
window
|
||||
.sendMessage('encryptAndPublishSymmetricKeyGroupChat', {
|
||||
groupId: groupId,
|
||||
previousData: secretKeyToSend,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.',
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-common-secret',
|
||||
label: `Published secret key for group ${groupId}: awaiting confirmation`,
|
||||
labelDone: `Published secret key for group ${groupId}: success!`,
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsForceShowCreationKeyPopup(false);
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
'Failed to encrypt and publish symmetric key for group chat:',
|
||||
error.message || 'An error occurred'
|
||||
);
|
||||
setIsLoading(false);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
padding: '25px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
maxWidth: '350px',
|
||||
background: '#444444'
|
||||
}}>
|
||||
<LoadingButton loading={isLoading} loadingPosition="start" color="warning" variant='contained' onClick={createCommonSecret}>Re-encrypt key</LoadingButton>
|
||||
{noSecretKey ? (
|
||||
<Box>
|
||||
<Typography>There is no group secret key. Be the first admin to publish one!</Typography>
|
||||
</Box>
|
||||
) : isOwner && secretKeyDetails && userInfo?.name && userInfo.name !== secretKeyDetails?.name ? (
|
||||
<Box>
|
||||
<Typography>The latest group secret key was published by a non-owner. As the owner of the group please re-encrypt the key as a safeguard</Typography>
|
||||
</Box>
|
||||
): isForceShowCreationKeyPopup ? null : (
|
||||
<Box>
|
||||
<Typography>The group member list has changed. Please re-encrypt the secret key.</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{
|
||||
<Box
|
||||
sx={{
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-end'
|
||||
}}>
|
||||
<Button onClick={()=> {
|
||||
setHideCommonKeyPopup(true)
|
||||
setIsForceShowCreationKeyPopup(false)
|
||||
}} size='small'>Hide</Button>
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
maxWidth: '350px',
|
||||
padding: '25px',
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
loadingPosition="start"
|
||||
color="warning"
|
||||
variant="contained"
|
||||
onClick={createCommonSecret}
|
||||
>
|
||||
Re-encrypt key
|
||||
</LoadingButton>
|
||||
|
||||
{noSecretKey ? (
|
||||
<Box>
|
||||
<Typography>
|
||||
There is no group secret key. Be the first admin to publish one!
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isOwner &&
|
||||
secretKeyDetails &&
|
||||
userInfo?.name &&
|
||||
userInfo.name !== secretKeyDetails?.name ? (
|
||||
<Box>
|
||||
<Typography>
|
||||
The latest group secret key was published by a non-owner. As the
|
||||
owner of the group please re-encrypt the key as a safeguard
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isForceShowCreationKeyPopup ? null : (
|
||||
<Box>
|
||||
<Typography>
|
||||
The group member list has changed. Please re-encrypt the secret key.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setHideCommonKeyPopup(true);
|
||||
setIsForceShowCreationKeyPopup(false);
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
Hide
|
||||
</Button>
|
||||
</Box>
|
||||
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -5,31 +5,22 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CreateCommonSecret } from './CreateCommonSecret';
|
||||
import { reusableGet } from '../../qdn/publish/pubish';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
objectToBase64,
|
||||
} from '../../qdn/encryption/group-encryption';
|
||||
import { ChatContainerComp } from './ChatContainer';
|
||||
import { ChatList } from './ChatList';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
import Tiptap from './TipTap';
|
||||
import {
|
||||
AuthenticatedContainerInnerTop,
|
||||
CustomButton,
|
||||
} from '../../styles/App-styles';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { AnnouncementList } from './AnnouncementList';
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { AnnouncementDiscussion } from './AnnouncementDiscussion';
|
||||
import {
|
||||
MyContext,
|
||||
@ -42,9 +33,11 @@ import {
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
|
||||
import { getRootHeight } from '../../utils/mobile/mobileUtils';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const requestQueueCommentCount = new RequestQueueWithPromise(3);
|
||||
|
||||
export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
|
||||
3
|
||||
);
|
||||
@ -106,7 +99,9 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
export const handleUnencryptedPublishes = (publishes) => {
|
||||
let publishesData = [];
|
||||
@ -117,10 +112,13 @@ export const handleUnencryptedPublishes = (publishes) => {
|
||||
if (decodedData) {
|
||||
publishesData.push({ decryptedData: decodedData });
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
return publishesData;
|
||||
};
|
||||
|
||||
export const GroupAnnouncements = ({
|
||||
selectedGroup,
|
||||
secretKey,
|
||||
@ -236,7 +234,9 @@ export const GroupAnnouncements = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const publishAnc = async ({ encryptedData, identifier }: any) => {
|
||||
@ -258,6 +258,7 @@ export const GroupAnnouncements = ({
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clearEditorContent = () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
@ -286,17 +287,21 @@ export const GroupAnnouncements = ({
|
||||
});
|
||||
setTempPublishedList(tempData);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const publishAnnouncement = async () => {
|
||||
try {
|
||||
pauseAllQueues();
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
await show({
|
||||
message: 'Would you like to perform a ARBITRARY transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
if (isSending) return;
|
||||
if (editorRef.current) {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
@ -379,8 +384,7 @@ export const GroupAnnouncements = ({
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
[secretKey]
|
||||
@ -422,11 +426,15 @@ export const GroupAnnouncements = ({
|
||||
isPrivate
|
||||
);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const checkNewMessages = React.useCallback(async () => {
|
||||
try {
|
||||
const identifier = `grp-${selectedGroup}-anc-`;
|
||||
@ -449,7 +457,9 @@ export const GroupAnnouncements = ({
|
||||
},
|
||||
isPrivate
|
||||
);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
setAnnouncements(responseData);
|
||||
return;
|
||||
@ -467,11 +477,13 @@ export const GroupAnnouncements = ({
|
||||
{ name: data.name, identifier: data.identifier },
|
||||
isPrivate
|
||||
);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
setAnnouncements((prev) => [...newArray, ...prev]);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
console.log(error);
|
||||
}
|
||||
}, [announcements, secretKey, selectedGroup]);
|
||||
|
||||
@ -523,10 +535,10 @@ export const GroupAnnouncements = ({
|
||||
: 'calc(100vh - 70px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
visibility: hide && 'hidden',
|
||||
position: hide && 'fixed',
|
||||
left: hide && '-1000px',
|
||||
position: hide && 'fixed',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AnnouncementDiscussion
|
||||
@ -546,54 +558,54 @@ export const GroupAnnouncements = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
visibility: hide && 'hidden',
|
||||
position: hide && 'fixed',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
position: hide && 'fixed',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: isMobile ? '8px' : '25px',
|
||||
fontSize: isMobile ? '16px' : '20px',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '20px',
|
||||
gap: '20px',
|
||||
justifyContent: 'center',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
fontSize: isMobile ? '16px' : '30px',
|
||||
fontSize: '30px',
|
||||
}}
|
||||
/>
|
||||
Group Announcements
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Spacer height={isMobile ? '0px' : '25px'} />
|
||||
<Spacer height={'25px'} />
|
||||
</div>
|
||||
|
||||
{!isLoading && combinedListTempAndReal?.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -620,31 +632,28 @@ export const GroupAnnouncements = ({
|
||||
{isAdmin && (
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
maxHeight: isMobile ? 'auto' : '400px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
maxHeight: '400px',
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
// height: '100%',
|
||||
}}
|
||||
>
|
||||
<Tiptap
|
||||
@ -656,14 +665,15 @@ export const GroupAnnouncements = ({
|
||||
setIsFocusedParent={setIsFocusedParent}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100&',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100&',
|
||||
}}
|
||||
>
|
||||
{isFocusedParent && (
|
||||
@ -678,43 +688,46 @@ export const GroupAnnouncements = ({
|
||||
// Unfocus the editor
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: 'var(--danger)',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{` Close`}
|
||||
</CustomButton>
|
||||
)}
|
||||
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
if (isSending) return;
|
||||
publishAnnouncement();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: isSending
|
||||
? theme.palette.background.default
|
||||
: theme.palette.background.paper,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,19 +1,6 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { GroupMail } from "../Group/Forum/GroupMail";
|
||||
import { MyContext, isMobile } from "../../App";
|
||||
import { getRootHeight } from "../../utils/mobile/mobileUtils";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { GroupMail } from '../Group/Forum/GroupMail';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
|
||||
export const GroupForum = ({
|
||||
selectedGroup,
|
||||
@ -23,12 +10,13 @@ export const GroupForum = ({
|
||||
isAdmin,
|
||||
myAddress,
|
||||
hide,
|
||||
defaultThread,
|
||||
defaultThread,
|
||||
setDefaultThread,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
}) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const [isMoved, setIsMoved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hide) {
|
||||
setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving
|
||||
@ -39,20 +27,27 @@ export const GroupForum = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
opacity: hide ? 0 : 1,
|
||||
visibility: hide && 'hidden',
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
left: hide && '-1000px'
|
||||
}}
|
||||
>
|
||||
<GroupMail isPrivate={isPrivate} hide={hide} getSecretKey={getSecretKey} selectedGroup={selectedGroup} userInfo={userInfo} secretKey={secretKey} defaultThread={defaultThread} setDefaultThread={setDefaultThread} />
|
||||
|
||||
</div>
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<GroupMail
|
||||
isPrivate={isPrivate}
|
||||
hide={hide}
|
||||
getSecretKey={getSecretKey}
|
||||
selectedGroup={selectedGroup}
|
||||
userInfo={userInfo}
|
||||
secretKey={secretKey}
|
||||
defaultThread={defaultThread}
|
||||
setDefaultThread={setDefaultThread}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,69 +1,68 @@
|
||||
import React, {
|
||||
forwardRef, useEffect, useImperativeHandle,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
const selectItem = index => {
|
||||
const item = props.items[index]
|
||||
|
||||
if (item) {
|
||||
props.command(item)
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index) => {
|
||||
const item = props.items[index];
|
||||
|
||||
if (item) {
|
||||
props.command(item);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex(
|
||||
(selectedIndex + props.items.length - 1) % props.items.length
|
||||
);
|
||||
};
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
||||
};
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex);
|
||||
};
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
|
||||
}
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length)
|
||||
}
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex)
|
||||
}
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="dropdown-menu">
|
||||
{props.items.length
|
||||
? props.items.map((item, index) => (
|
||||
<button
|
||||
className={index === selectedIndex ? 'is-selected' : ''}
|
||||
key={item.id || index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))
|
||||
: <div className="item">No result</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="dropdown-menu">
|
||||
{props.items.length ? (
|
||||
props.items.map((item, index) => (
|
||||
<button
|
||||
className={index === selectedIndex ? 'is-selected' : ''}
|
||||
key={item.id || index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="item">No result</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,28 +1,29 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import DOMPurify from 'dompurify';
|
||||
import './styles.css';
|
||||
import './chat.css';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { Embed } from '../Embeds/Embed';
|
||||
import { Box, useTheme } from '@mui/material';
|
||||
|
||||
export const extractComponents = (url) => {
|
||||
if (!url || !url.startsWith("qortal://")) {
|
||||
if (!url || !url.startsWith('qortal://')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip links starting with "qortal://use-"
|
||||
if (url.startsWith("qortal://use-")) {
|
||||
if (url.startsWith('qortal://use-')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
url = url.replace(/^(qortal\:\/\/)/, "");
|
||||
if (url.includes("/")) {
|
||||
let parts = url.split("/");
|
||||
url = url.replace(/^(qortal\:\/\/)/, '');
|
||||
if (url.includes('/')) {
|
||||
let parts = url.split('/');
|
||||
const service = parts[0].toUpperCase();
|
||||
parts.shift();
|
||||
const name = parts[0];
|
||||
parts.shift();
|
||||
let identifier;
|
||||
const path = parts.join("/");
|
||||
const path = parts.join('/');
|
||||
return { service, name, identifier, path };
|
||||
}
|
||||
|
||||
@ -64,8 +65,7 @@ function processText(input) {
|
||||
}
|
||||
|
||||
const linkify = (text) => {
|
||||
if (!text) return ""; // Return an empty string if text is null or undefined
|
||||
|
||||
if (!text) return ''; // Return an empty string if text is null or undefined
|
||||
let textFormatted = text;
|
||||
const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g;
|
||||
textFormatted = text.replace(urlPattern, (url) => {
|
||||
@ -75,22 +75,68 @@ const linkify = (text) => {
|
||||
return processText(textFormatted);
|
||||
};
|
||||
|
||||
|
||||
export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
const sanitizedContent = useMemo(()=> {
|
||||
const sanitizedContent = useMemo(() => {
|
||||
return DOMPurify.sanitize(linkify(htmlContent), {
|
||||
ALLOWED_TAGS: [
|
||||
'a', 'b', 'i', 'em', 'strong', 'p', 'br', 'div', 'span', 'img',
|
||||
'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 's', 'hr'
|
||||
'a',
|
||||
'b',
|
||||
'i',
|
||||
'em',
|
||||
'strong',
|
||||
'p',
|
||||
'br',
|
||||
'div',
|
||||
'span',
|
||||
'img',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'code',
|
||||
'pre',
|
||||
'table',
|
||||
'thead',
|
||||
'tbody',
|
||||
'tr',
|
||||
'th',
|
||||
'td',
|
||||
's',
|
||||
'hr',
|
||||
],
|
||||
ALLOWED_ATTR: [
|
||||
'href', 'target', 'rel', 'class', 'src', 'alt', 'title',
|
||||
'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing', 'data-url'
|
||||
'href',
|
||||
'target',
|
||||
'rel',
|
||||
'class',
|
||||
'src',
|
||||
'alt',
|
||||
'title',
|
||||
'width',
|
||||
'height',
|
||||
'style',
|
||||
'align',
|
||||
'valign',
|
||||
'colspan',
|
||||
'rowspan',
|
||||
'border',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'data-url',
|
||||
],
|
||||
}).replace(/<span[^>]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g, '');
|
||||
}, [htmlContent])
|
||||
}).replace(
|
||||
/<span[^>]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g,
|
||||
''
|
||||
);
|
||||
}, [htmlContent]);
|
||||
|
||||
const handleClick = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -98,7 +144,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
const target = e.target;
|
||||
if (target.tagName === 'A') {
|
||||
const href = target.getAttribute('href');
|
||||
if(window?.electronAPI){
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.openExternal(href);
|
||||
} else {
|
||||
window.open(href, '_system');
|
||||
@ -106,32 +152,32 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
} else if (target.getAttribute('data-url')) {
|
||||
const url = target.getAttribute('data-url');
|
||||
|
||||
let copyUrl = url
|
||||
let copyUrl = url;
|
||||
|
||||
try {
|
||||
copyUrl = copyUrl.replace(/^(qortal:\/\/)/, '')
|
||||
if (copyUrl.startsWith('use-')) {
|
||||
// Handle the new 'use' format
|
||||
const parts = copyUrl.split('/')
|
||||
const type = parts[0].split('-')[1] // e.g., 'group' from 'use-group'
|
||||
parts.shift()
|
||||
const action = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., 'invite' from 'action-invite'
|
||||
parts.shift()
|
||||
const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null // e.g., 'groupid' from 'groupid-321'
|
||||
const id = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., '321' from 'groupid-321'
|
||||
if(action === 'join'){
|
||||
executeEvent("globalActionJoinGroup", { groupId: id});
|
||||
return
|
||||
try {
|
||||
copyUrl = copyUrl.replace(/^(qortal:\/\/)/, '');
|
||||
if (copyUrl.startsWith('use-')) {
|
||||
// Handle the new 'use' format
|
||||
const parts = copyUrl.split('/');
|
||||
const type = parts[0].split('-')[1]; // e.g., 'group' from 'use-group'
|
||||
parts.shift();
|
||||
const action = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., 'invite' from 'action-invite'
|
||||
parts.shift();
|
||||
const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null; // e.g., 'groupid' from 'groupid-321'
|
||||
const id = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., '321' from 'groupid-321'
|
||||
if (action === 'join') {
|
||||
executeEvent('globalActionJoinGroup', { groupId: id });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//error
|
||||
}
|
||||
} catch (error) {
|
||||
//error
|
||||
}
|
||||
const res = extractComponents(url);
|
||||
if (res) {
|
||||
const { service, name, identifier, path } = res;
|
||||
executeEvent("addTab", { data: { service, name, identifier, path } });
|
||||
executeEvent("open-apps-mode", { });
|
||||
executeEvent('addTab', { data: { service, name, identifier, path } });
|
||||
executeEvent('open-apps-mode', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -141,19 +187,24 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
let embedData = null;
|
||||
|
||||
if (embedLink) {
|
||||
embedData = embedLink[0]
|
||||
embedData = embedLink[0];
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{embedLink && (
|
||||
<Embed embedLink={embedData} />
|
||||
)}
|
||||
<div
|
||||
className={`tiptap ${isReply ? 'isReply' : ''}`}
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</>
|
||||
<Box
|
||||
sx={{
|
||||
'--text-primary': theme.palette.text.primary,
|
||||
'--text-secondary': theme.palette.text.secondary,
|
||||
'--background-default': theme.palette.background.default,
|
||||
'--background-secondary': theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
{embedLink && <Embed embedLink={embedData} />}
|
||||
<div
|
||||
className={`tiptap ${isReply ? 'isReply' : ''}`}
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { NodeViewWrapper } from '@tiptap/react';
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
const ResizableImage = ({ node, updateAttributes, selected }) => {
|
||||
const imgRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const startResizing = (e) => {
|
||||
e.preventDefault();
|
||||
@ -40,18 +42,23 @@ const ResizableImage = ({ node, updateAttributes, selected }) => {
|
||||
src={node.attrs.src}
|
||||
alt={node.attrs.alt || ''}
|
||||
title={node.attrs.title || ''}
|
||||
style={{ width: node.attrs.width || 'auto', display: 'block', margin: '0 auto' }}
|
||||
style={{
|
||||
width: node.attrs.width || 'auto',
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
draggable={false} // Prevent image dragging
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
bottom: 0,
|
||||
cursor: 'nwse-resize',
|
||||
height: '10px',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: 'gray',
|
||||
cursor: 'nwse-resize',
|
||||
zIndex: 1, // Ensure the resize handle is above other content
|
||||
}}
|
||||
onMouseDown={startResizing}
|
||||
|
@ -1,42 +1,36 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { Color } from "@tiptap/extension-color";
|
||||
import ListItem from "@tiptap/extension-list-item";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import FormatBoldIcon from "@mui/icons-material/FormatBold";
|
||||
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
|
||||
import StrikethroughSIcon from "@mui/icons-material/StrikethroughS";
|
||||
import FormatClearIcon from "@mui/icons-material/FormatClear";
|
||||
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
||||
import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
import ImageIcon from "@mui/icons-material/Image"; // Import Image icon
|
||||
import FormatQuoteIcon from "@mui/icons-material/FormatQuote";
|
||||
import HorizontalRuleIcon from "@mui/icons-material/HorizontalRule";
|
||||
import UndoIcon from "@mui/icons-material/Undo";
|
||||
import RedoIcon from "@mui/icons-material/Redo";
|
||||
import FormatHeadingIcon from "@mui/icons-material/FormatSize";
|
||||
import DeveloperModeIcon from "@mui/icons-material/DeveloperMode";
|
||||
import Compressor from "compressorjs";
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { Color } from '@tiptap/extension-color';
|
||||
import ListItem from '@tiptap/extension-list-item';
|
||||
import TextStyle from '@tiptap/extension-text-style';
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import FormatBoldIcon from '@mui/icons-material/FormatBold';
|
||||
import FormatItalicIcon from '@mui/icons-material/FormatItalic';
|
||||
import StrikethroughSIcon from '@mui/icons-material/StrikethroughS';
|
||||
import FormatClearIcon from '@mui/icons-material/FormatClear';
|
||||
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
|
||||
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
||||
import CodeIcon from '@mui/icons-material/Code';
|
||||
import ImageIcon from '@mui/icons-material/Image'; // Import Image icon
|
||||
import FormatQuoteIcon from '@mui/icons-material/FormatQuote';
|
||||
import HorizontalRuleIcon from '@mui/icons-material/HorizontalRule';
|
||||
import UndoIcon from '@mui/icons-material/Undo';
|
||||
import RedoIcon from '@mui/icons-material/Redo';
|
||||
import FormatHeadingIcon from '@mui/icons-material/FormatSize';
|
||||
import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
|
||||
import Compressor from 'compressorjs';
|
||||
import Mention from '@tiptap/extension-mention';
|
||||
import ImageResize from "tiptap-extension-resize-image"; // Import the ResizeImage extension
|
||||
import { isMobile } from "../../App";
|
||||
import tippy from "tippy.js";
|
||||
import "tippy.js/dist/tippy.css";
|
||||
import Popover from '@mui/material/Popover';
|
||||
import List from '@mui/material/List';
|
||||
import ListItemMui from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { ReactRenderer } from '@tiptap/react'
|
||||
import MentionList from './MentionList.jsx'
|
||||
import { useRecoilState } from "recoil";
|
||||
import { isDisabledEditorEnterAtom } from "../../atoms/global.js";
|
||||
import { Box, Checkbox, Typography } from "@mui/material";
|
||||
import ImageResize from 'tiptap-extension-resize-image'; // Import the ResizeImage extension
|
||||
import { isMobile } from '../../App';
|
||||
import tippy from 'tippy.js';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { ReactRenderer } from '@tiptap/react';
|
||||
import MentionList from './MentionList.jsx';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDisabledEditorEnterAtom } from '../../atoms/global.js';
|
||||
import { Box, Checkbox, Typography, useTheme } from '@mui/material';
|
||||
|
||||
function textMatcher(doc, from) {
|
||||
const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
|
||||
@ -47,9 +41,16 @@ function textMatcher(doc, from) {
|
||||
const query = match[0];
|
||||
return { start, query };
|
||||
}
|
||||
const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEditorEnter }) => {
|
||||
|
||||
const MenuBar = ({
|
||||
setEditorRef,
|
||||
isChat,
|
||||
isDisabledEditorEnter,
|
||||
setIsDisabledEditorEnter,
|
||||
}) => {
|
||||
const { editor } = useCurrentEditor();
|
||||
const fileInputRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
if (!editor) {
|
||||
return null;
|
||||
@ -67,15 +68,15 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
new Compressor(file, {
|
||||
quality: 0.6,
|
||||
maxWidth: 1200,
|
||||
mimeType: "image/webp",
|
||||
mimeType: 'image/webp',
|
||||
success(result) {
|
||||
compressedFile = new File([result], "image.webp", {
|
||||
type: "image/webp",
|
||||
compressedFile = new File([result], 'image.webp', {
|
||||
type: 'image/webp',
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
error(err) {
|
||||
console.error("Image compression error:", err);
|
||||
console.error('Image compression error:', err);
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -87,9 +88,9 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setImage({ src: url, style: "width: auto" })
|
||||
.setImage({ src: url, style: 'width: auto' })
|
||||
.run();
|
||||
fileInputRef.current.value = "";
|
||||
fileInputRef.current.value = '';
|
||||
};
|
||||
reader.readAsDataURL(compressedFile);
|
||||
}
|
||||
@ -102,7 +103,7 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
const handlePaste = (event) => {
|
||||
const items = event.clipboardData.items;
|
||||
for (const item of items) {
|
||||
if (item.type.startsWith("image/")) {
|
||||
if (item.type.startsWith('image/')) {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
event.preventDefault(); // Prevent the default paste behavior
|
||||
@ -114,24 +115,29 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
editor.view.dom.addEventListener("paste", handlePaste);
|
||||
editor.view.dom.addEventListener('paste', handlePaste);
|
||||
return () => {
|
||||
editor.view.dom.removeEventListener("paste", handlePaste);
|
||||
editor.view.dom.removeEventListener('paste', handlePaste);
|
||||
};
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<div className="control-group">
|
||||
<div className="button-group" style={{
|
||||
display: 'flex'
|
||||
}}>
|
||||
<div
|
||||
className="button-group"
|
||||
style={{
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
disabled={!editor.can().chain().focus().toggleBold().run()}
|
||||
sx={{
|
||||
color: editor.isActive("bold") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('bold')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatBoldIcon />
|
||||
@ -140,8 +146,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
||||
sx={{
|
||||
color: editor.isActive("italic") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('italic')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatItalicIcon />
|
||||
@ -150,8 +158,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
||||
sx={{
|
||||
color: editor.isActive("strike") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('strike')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<StrikethroughSIcon />
|
||||
@ -160,8 +170,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
disabled={!editor.can().chain().focus().toggleCode().run()}
|
||||
sx={{
|
||||
color: editor.isActive("code") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('code')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<CodeIcon />
|
||||
@ -170,13 +182,13 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
onClick={() => editor.chain().focus().unsetAllMarks().run()}
|
||||
sx={{
|
||||
color:
|
||||
editor.isActive("bold") ||
|
||||
editor.isActive("italic") ||
|
||||
editor.isActive("strike") ||
|
||||
editor.isActive("code")
|
||||
? "white"
|
||||
: "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
editor.isActive('bold') ||
|
||||
editor.isActive('italic') ||
|
||||
editor.isActive('strike') ||
|
||||
editor.isActive('code')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatClearIcon />
|
||||
@ -184,8 +196,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
sx={{
|
||||
color: editor.isActive("bulletList") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('bulletList')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatListBulletedIcon />
|
||||
@ -193,8 +207,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
sx={{
|
||||
color: editor.isActive("orderedList") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('orderedList')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatListNumberedIcon />
|
||||
@ -202,8 +218,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
sx={{
|
||||
color: editor.isActive("codeBlock") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('codeBlock')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<DeveloperModeIcon />
|
||||
@ -211,8 +229,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
sx={{
|
||||
color: editor.isActive("blockquote") ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('blockquote')
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatQuoteIcon />
|
||||
@ -220,7 +240,7 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
disabled={!editor.can().chain().focus().setHorizontalRule().run()}
|
||||
sx={{ color: "gray", padding: isMobile ? "5px" : "revert" }}
|
||||
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
|
||||
>
|
||||
<HorizontalRuleIcon />
|
||||
</IconButton>
|
||||
@ -229,8 +249,10 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||
}
|
||||
sx={{
|
||||
color: editor.isActive("heading", { level: 1 }) ? "white" : "gray",
|
||||
padding: isMobile ? "5px" : "revert",
|
||||
color: editor.isActive('heading', { level: 1 })
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<FormatHeadingIcon fontSize="small" />
|
||||
@ -238,66 +260,68 @@ const MenuBar = ({ setEditorRef, isChat, isDisabledEditorEnter, setIsDisabledEdi
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={!editor.can().chain().focus().undo().run()}
|
||||
sx={{ color: "gray", padding: isMobile ? "5px" : "revert" }}
|
||||
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
|
||||
>
|
||||
<UndoIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
disabled={!editor.can().chain().focus().redo().run()}
|
||||
sx={{ color: "gray" }}
|
||||
sx={{ color: 'gray' }}
|
||||
>
|
||||
<RedoIcon />
|
||||
</IconButton>
|
||||
{isChat && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginLeft: '5px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={()=> {
|
||||
setIsDisabledEditorEnter(!isDisabledEditorEnter)
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
|
||||
checked={isDisabledEditorEnter}
|
||||
sx={{
|
||||
"&.Mui-checked": {
|
||||
color: "gray", // Customize the color when checked
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "gray",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
color: 'gray'
|
||||
}}
|
||||
>
|
||||
disable enter
|
||||
</Typography>
|
||||
</Box>
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '5px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsDisabledEditorEnter(!isDisabledEditorEnter);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
checked={isDisabledEditorEnter}
|
||||
sx={{
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .MuiSvgIcon-root': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
disable enter
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{!isChat && (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={triggerImageUpload}
|
||||
sx={{ color: "gray", padding: isMobile ? "5px" : "revert" }}
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
padding: isMobile ? '5px' : 'revert',
|
||||
}}
|
||||
>
|
||||
<ImageIcon />
|
||||
</IconButton>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
style={{ display: 'none' }}
|
||||
onChange={(event) => handleImageUpload(event.target.files[0])}
|
||||
accept="image/*"
|
||||
/>
|
||||
@ -322,7 +346,7 @@ const extensions = [
|
||||
},
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder: "Start typing here...",
|
||||
placeholder: 'Start typing here...',
|
||||
}),
|
||||
ImageResize,
|
||||
];
|
||||
@ -340,12 +364,13 @@ export default ({
|
||||
overrideMobile,
|
||||
customEditorHeight,
|
||||
membersWithNames,
|
||||
enableMentions
|
||||
enableMentions,
|
||||
}) => {
|
||||
const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useRecoilState(isDisabledEditorEnterAtom)
|
||||
|
||||
const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useRecoilState(
|
||||
isDisabledEditorEnterAtom
|
||||
);
|
||||
const extensionsFiltered = isChat
|
||||
? extensions.filter((item) => item?.name !== "image")
|
||||
? extensions.filter((item) => item?.name !== 'image')
|
||||
: extensions;
|
||||
const editorRef = useRef(null);
|
||||
const setEditorRefFunc = (editorInstance) => {
|
||||
@ -359,20 +384,14 @@ export default ({
|
||||
// { id: 3, label: 'Charlie' },
|
||||
// ];
|
||||
|
||||
|
||||
|
||||
const users = useMemo(()=> {
|
||||
return (membersWithNames || [])?.map((item)=> {
|
||||
const users = useMemo(() => {
|
||||
return (membersWithNames || [])?.map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
label: item
|
||||
}
|
||||
})
|
||||
}, [membersWithNames])
|
||||
|
||||
|
||||
|
||||
|
||||
label: item,
|
||||
};
|
||||
});
|
||||
}, [membersWithNames]);
|
||||
|
||||
const usersRef = useRef([]);
|
||||
useEffect(() => {
|
||||
@ -386,13 +405,13 @@ export default ({
|
||||
|
||||
const handleBlur = () => {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === "<p></p>") {
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
|
||||
// Set focus state based on content
|
||||
}
|
||||
};
|
||||
|
||||
const additionalExtensions = useMemo(()=> {
|
||||
if(!enableMentions) return []
|
||||
const additionalExtensions = useMemo(() => {
|
||||
if (!enableMentions) return [];
|
||||
return [
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
@ -409,122 +428,129 @@ export default ({
|
||||
let popup; // Reference to the Tippy.js instance
|
||||
let component;
|
||||
|
||||
return {
|
||||
onStart: props => {
|
||||
component = new ReactRenderer(MentionList, {
|
||||
props,
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
},
|
||||
|
||||
onUpdate(props) {
|
||||
component.updateProps(props)
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
},
|
||||
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props)
|
||||
},
|
||||
|
||||
onExit() {
|
||||
popup[0].destroy()
|
||||
component.destroy()
|
||||
},
|
||||
}
|
||||
return {
|
||||
onStart: (props) => {
|
||||
component = new ReactRenderer(MentionList, {
|
||||
props,
|
||||
editor: props.editor,
|
||||
});
|
||||
|
||||
if (!props.clientRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
});
|
||||
},
|
||||
|
||||
onUpdate(props) {
|
||||
component.updateProps(props);
|
||||
|
||||
if (!props.clientRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
});
|
||||
},
|
||||
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props);
|
||||
},
|
||||
|
||||
onExit() {
|
||||
popup[0].destroy();
|
||||
component.destroy();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
})
|
||||
]
|
||||
}, [enableMentions])
|
||||
}),
|
||||
];
|
||||
}, [enableMentions]);
|
||||
|
||||
const handleSetIsDisabledEditorEnter = useCallback((val)=> {
|
||||
setIsDisabledEditorEnter(val)
|
||||
const handleSetIsDisabledEditorEnter = useCallback((val) => {
|
||||
setIsDisabledEditorEnter(val);
|
||||
localStorage.setItem('settings-disable-editor-enter', JSON.stringify(val));
|
||||
|
||||
}, [])
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
height: '100%'
|
||||
}}>
|
||||
<EditorProvider
|
||||
slotBefore={
|
||||
(isFocusedParent || !isMobile || overrideMobile) && (
|
||||
<MenuBar setEditorRef={setEditorRefFunc} isChat={isChat} isDisabledEditorEnter={isDisabledEditorEnter} setIsDisabledEditorEnter={handleSetIsDisabledEditorEnter} />
|
||||
)
|
||||
}
|
||||
extensions={[...extensionsFiltered, ...additionalExtensions
|
||||
]}
|
||||
content={content}
|
||||
onCreate={({ editor }) => {
|
||||
editor.on("focus", handleFocus); // Listen for focus event
|
||||
editor.on("blur", handleBlur); // Listen for blur event
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onUpdate={({ editor }) => {
|
||||
editor.on('focus', handleFocus); // Ensure focus is updated
|
||||
editor.on('blur', handleBlur); // Ensure blur is updated
|
||||
}}
|
||||
editorProps={{
|
||||
attributes: {
|
||||
class: "tiptap-prosemirror",
|
||||
style:
|
||||
isMobile ?
|
||||
`overflow: auto; min-height: ${
|
||||
customEditorHeight ? "200px" : "0px"
|
||||
}; max-height:calc(100svh - ${customEditorHeight || "140px"})`: `overflow: auto; max-height: 250px`,
|
||||
},
|
||||
handleKeyDown(view, event) {
|
||||
if (!disableEnter && !isDisabledEditorEnter && event.key === "Enter") {
|
||||
if (event.shiftKey) {
|
||||
view.dispatch(
|
||||
view.state.tr.replaceSelectionWith(
|
||||
view.state.schema.nodes.hardBreak.create()
|
||||
)
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
if (typeof onEnter === "function") {
|
||||
onEnter();
|
||||
>
|
||||
<EditorProvider
|
||||
slotBefore={
|
||||
(isFocusedParent || !isMobile || overrideMobile) && (
|
||||
<MenuBar
|
||||
setEditorRef={setEditorRefFunc}
|
||||
isChat={isChat}
|
||||
isDisabledEditorEnter={isDisabledEditorEnter}
|
||||
setIsDisabledEditorEnter={handleSetIsDisabledEditorEnter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
extensions={[...extensionsFiltered, ...additionalExtensions]}
|
||||
content={content}
|
||||
onCreate={({ editor }) => {
|
||||
editor.on('focus', handleFocus); // Listen for focus event
|
||||
editor.on('blur', handleBlur); // Listen for blur event
|
||||
}}
|
||||
onUpdate={({ editor }) => {
|
||||
editor.on('focus', handleFocus); // Ensure focus is updated
|
||||
editor.on('blur', handleBlur); // Ensure blur is updated
|
||||
}}
|
||||
editorProps={{
|
||||
attributes: {
|
||||
class: 'tiptap-prosemirror',
|
||||
style: isMobile
|
||||
? `overflow: auto; min-height: ${
|
||||
customEditorHeight ? '200px' : '0px'
|
||||
}; max-height:calc(100svh - ${customEditorHeight || '140px'})`
|
||||
: `overflow: auto; max-height: 250px`,
|
||||
},
|
||||
handleKeyDown(view, event) {
|
||||
if (
|
||||
!disableEnter &&
|
||||
!isDisabledEditorEnter &&
|
||||
event.key === 'Enter'
|
||||
) {
|
||||
if (event.shiftKey) {
|
||||
view.dispatch(
|
||||
view.state.tr.replaceSelectionWith(
|
||||
view.state.schema.nodes.hardBreak.create()
|
||||
)
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
if (typeof onEnter === 'function') {
|
||||
onEnter();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
return false;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
.tiptap {
|
||||
margin-top: 0;
|
||||
color: white; /* Set default font color to white */
|
||||
color: var(--text-primary);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
line-height: 1.1;
|
||||
margin-top: 2.5rem;
|
||||
text-wrap: pretty;
|
||||
color: white; /* Ensure heading font color is white */
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tiptap h1,
|
||||
@ -55,18 +55,18 @@
|
||||
|
||||
/* Code and preformatted text styles */
|
||||
.tiptap code {
|
||||
background-color: #27282c; /* Set code background color to #27282c */
|
||||
background-color: var(--background-default);
|
||||
border-radius: 0.4rem;
|
||||
color: white; /* Ensure inline code text color is white */
|
||||
color: var(--text-primary);
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25em 0.3em;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.tiptap pre {
|
||||
background: #27282c; /* Set code block background color to #27282c */
|
||||
background: var(--background-default);
|
||||
border-radius: 0.5rem;
|
||||
color: white; /* Ensure code block text color is white */
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
@ -86,7 +86,7 @@
|
||||
border-left: 3px solid var(--gray-3);
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1rem;
|
||||
color: white; /* Ensure blockquote text color is white */
|
||||
color: var(--text-primary);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
@ -102,49 +102,49 @@
|
||||
|
||||
.tiptap p {
|
||||
font-size: 16px;
|
||||
color: white; /* Ensure paragraph text color is white */
|
||||
color: var(--text-primary);
|
||||
margin: 0px;
|
||||
}
|
||||
.tiptap p.is-editor-empty:first-child::before {
|
||||
color: #adb5bd;
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tiptap p:empty::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
}
|
||||
.tiptap p.is-editor-empty:first-child::before {
|
||||
color: var(--text-primary);
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tiptap p:empty::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tiptap a {
|
||||
color: cadetblue
|
||||
color: cadetblue;
|
||||
}
|
||||
|
||||
.tiptap img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.isReply p {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.tiptap [data-type="mention"] {
|
||||
.tiptap [data-type='mention'] {
|
||||
box-decoration-break: clone;
|
||||
color: lightblue;
|
||||
color: var(--text-secondary);
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
.unread-divider {
|
||||
border-bottom: 1px solid var(--text-primary);
|
||||
border-radius: 2px;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 90%;
|
||||
color: white;
|
||||
border-bottom: 1px solid white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.mention-item {
|
||||
@ -169,11 +169,10 @@
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
&:hover,
|
||||
&:hover.is-selected {
|
||||
background-color: gray;
|
||||
background-color: var(--background-default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -73,6 +73,7 @@ export const CoreSyncStatus = () => {
|
||||
|
||||
let imagePath = syncingImg;
|
||||
let message = `Synchronizing`;
|
||||
|
||||
if (isMintingPossible && !isUsingGateway) {
|
||||
imagePath = syncedMintingImg;
|
||||
message = `${isSynchronizing ? 'Synchronizing' : 'Synchronized'} ${'(Minting)'}`;
|
||||
@ -101,15 +102,17 @@ export const CoreSyncStatus = () => {
|
||||
<span>
|
||||
<img
|
||||
src={imagePath}
|
||||
style={{ height: 'auto', width: '24px' }}
|
||||
style={{ height: 'auto', width: '35px' }}
|
||||
alt="sync status"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div
|
||||
className="bottom"
|
||||
style={{
|
||||
right: 'unset',
|
||||
left: '0px',
|
||||
left: '55px',
|
||||
top: '10px',
|
||||
}}
|
||||
>
|
||||
<h3>Core Information</h3>
|
||||
@ -134,7 +137,6 @@ export const CoreSyncStatus = () => {
|
||||
{isUsingGateway?.toString()}
|
||||
</span>
|
||||
</h4>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -22,27 +22,27 @@ export const IconWrapper = ({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
flexDirection: 'column',
|
||||
height: customWidth ? customWidth : disableWidth ? 'auto' : '89px',
|
||||
width: customWidth ? customWidth : disableWidth ? 'auto' : '89px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: selected
|
||||
? theme.palette.background.default
|
||||
: 'transparent',
|
||||
borderRadius: '50%',
|
||||
color: color ? color : theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
height: customWidth ? customWidth : disableWidth ? 'auto' : '89px',
|
||||
justifyContent: 'center',
|
||||
width: customWidth ? customWidth : disableWidth ? 'auto' : '89px',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@ -68,18 +68,20 @@ export const DesktopFooter = ({
|
||||
const [isEnabledDevMode, setIsEnabledDevMode] =
|
||||
useRecoilState(enabledDevModeAtom);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
if (hide) return;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100px', // Footer height
|
||||
zIndex: 1,
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -122,8 +124,8 @@ export const DesktopFooter = ({
|
||||
hasUnreadGroups
|
||||
? 'var(--danger)'
|
||||
: isGroups
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
@ -141,8 +143,8 @@ export const DesktopFooter = ({
|
||||
hasUnreadDirects
|
||||
? 'var(--danger)'
|
||||
: isDirects
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
|
@ -7,6 +7,7 @@ import { useRecoilState } from 'recoil';
|
||||
import { enabledDevModeAtom } from '../atoms/global';
|
||||
import { AppsIcon } from '../assets/Icons/AppsIcon';
|
||||
import ThemeSelector from './Theme/ThemeSelector';
|
||||
import { CoreSyncStatus } from './CoreSyncStatus';
|
||||
|
||||
export const DesktopSideBar = ({
|
||||
goToHome,
|
||||
@ -30,19 +31,28 @@ export const DesktopSideBar = ({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '60px',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
height: '100vh',
|
||||
width: '60px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '70px',
|
||||
height: '70px',
|
||||
paddingTop: '23px',
|
||||
}}
|
||||
>
|
||||
<CoreSyncStatus />
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
paddingTop: '23px',
|
||||
}}
|
||||
onClick={() => {
|
||||
goToHome();
|
||||
@ -51,10 +61,13 @@ export const DesktopSideBar = ({
|
||||
<HomeIcon
|
||||
height={34}
|
||||
color={
|
||||
desktopViewMode === 'home' ? 'white' : 'rgba(250, 250, 250, 0.5)'
|
||||
desktopViewMode === 'home'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopViewMode('apps');
|
||||
@ -63,17 +76,22 @@ export const DesktopSideBar = ({
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
|
||||
color={
|
||||
isApps ? theme.palette.text.primary : theme.palette.text.secondary
|
||||
}
|
||||
label="Apps"
|
||||
selected={isApps}
|
||||
disableWidth
|
||||
>
|
||||
<AppsIcon
|
||||
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
|
||||
color={
|
||||
isApps ? theme.palette.text.primary : theme.palette.text.secondary
|
||||
}
|
||||
height={30}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopViewMode('chat');
|
||||
@ -98,24 +116,7 @@ export const DesktopSideBar = ({
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
{/* <ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopSideView("groups");
|
||||
toggleSideViewGroups()
|
||||
}}
|
||||
>
|
||||
<HubsIcon
|
||||
height={30}
|
||||
color={
|
||||
hasUnreadGroups
|
||||
? "var(--danger)"
|
||||
: isGroups
|
||||
? "white"
|
||||
: "rgba(250, 250, 250, 0.5)"
|
||||
}
|
||||
/>
|
||||
|
||||
</ButtonBase> */}
|
||||
|
||||
<Save isDesktop disableWidth myName={myName} />
|
||||
{/* <CoreSyncStatus imageSize="30px" position="left" /> */}
|
||||
{isEnabledDevMode && (
|
||||
@ -126,7 +127,9 @@ export const DesktopSideBar = ({
|
||||
>
|
||||
<IconWrapper
|
||||
color={
|
||||
desktopViewMode === 'dev' ? 'white' : 'rgba(250, 250, 250, 0.5)'
|
||||
desktopViewMode === 'dev'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Dev"
|
||||
disableWidth
|
||||
|
@ -1,32 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import Button from '@mui/material/Button';
|
||||
import List from '@mui/material/List';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import InboxIcon from '@mui/icons-material/MoveToInbox';
|
||||
import MailIcon from '@mui/icons-material/Mail';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { isMobile } from '../../App';
|
||||
export const DrawerComponent = ({open, setOpen, children}) => {
|
||||
|
||||
export const DrawerComponent = ({ open, setOpen, children }) => {
|
||||
const toggleDrawer = (newOpen: boolean) => () => {
|
||||
setOpen(newOpen);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Drawer open={open} onClose={toggleDrawer(false)}>
|
||||
<Box sx={{ width: isMobile ? '100vw' : '400px', height: '100%' }} role="presentation">
|
||||
|
||||
{children}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{ width: isMobile ? '100vw' : '400px', height: '100%' }}
|
||||
role="presentation"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,22 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
|
||||
export const DrawerUserLookup = ({open, setOpen, children}) => {
|
||||
|
||||
export const DrawerUserLookup = ({ open, setOpen, children }) => {
|
||||
const toggleDrawer = (newOpen: boolean) => () => {
|
||||
setOpen(newOpen);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Drawer disableEnforceFocus hideBackdrop={true} open={open} onClose={toggleDrawer(false)}>
|
||||
<Box sx={{ width: '70vw', height: '100%', maxWidth: '1000px' }} role="presentation">
|
||||
|
||||
{children}
|
||||
</Box>
|
||||
<Drawer
|
||||
disableEnforceFocus
|
||||
hideBackdrop={true}
|
||||
open={open}
|
||||
onClose={toggleDrawer(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: '70vw', height: '100%', maxWidth: '1000px' }}
|
||||
role="presentation"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,42 +1,46 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { extractComponents } from '../Chat/MessageDisplay';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
|
||||
import { extractComponents } from "../Chat/MessageDisplay";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
import { base64ToBlobUrl } from "../../utils/fileReading";
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
||||
import { blobControllerAtom, blobKeySelector, resourceKeySelector, selectedGroupIdAtom } from "../../atoms/global";
|
||||
import { parseQortalLink } from "./embed-utils";
|
||||
import { PollCard } from "./PollEmbed";
|
||||
import { ImageCard } from "./ImageEmbed";
|
||||
import { AttachmentCard } from "./AttachmentEmbed";
|
||||
import { decodeIfEncoded } from "../../utils/decode";
|
||||
import { base64ToBlobUrl } from '../../utils/fileReading';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
blobControllerAtom,
|
||||
blobKeySelector,
|
||||
resourceKeySelector,
|
||||
selectedGroupIdAtom,
|
||||
} from '../../atoms/global';
|
||||
import { parseQortalLink } from './embed-utils';
|
||||
import { PollCard } from './PollEmbed';
|
||||
import { ImageCard } from './ImageEmbed';
|
||||
import { AttachmentCard } from './AttachmentEmbed';
|
||||
import { decodeIfEncoded } from '../../utils/decode';
|
||||
|
||||
const getPoll = async (name) => {
|
||||
const pollName = name;
|
||||
const url = `${getBaseApiReact()}/polls/${pollName}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
if (responseData?.message?.includes("POLL_NO_EXISTS")) {
|
||||
throw new Error("POLL_NO_EXISTS");
|
||||
if (responseData?.message?.includes('POLL_NO_EXISTS')) {
|
||||
throw new Error('POLL_NO_EXISTS');
|
||||
} else if (responseData?.pollName) {
|
||||
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
|
||||
|
||||
const responseVotes = await fetch(urlVotes, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
@ -49,56 +53,64 @@ const getPoll = async (name) => {
|
||||
};
|
||||
|
||||
export const Embed = ({ embedLink }) => {
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [poll, setPoll] = useState(null);
|
||||
const [type, setType] = useState("");
|
||||
const [type, setType] = useState('');
|
||||
const hasFetched = useRef(false);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [external, setExternal] = useState(null);
|
||||
const [imageUrl, setImageUrl] = useState("");
|
||||
const [imageUrl, setImageUrl] = useState('');
|
||||
const [parsedData, setParsedData] = useState(null);
|
||||
const setBlobs = useSetRecoilState(blobControllerAtom);
|
||||
const [selectedGroupId] = useRecoilState(selectedGroupIdAtom)
|
||||
const resourceData = useMemo(()=> {
|
||||
const [selectedGroupId] = useRecoilState(selectedGroupIdAtom);
|
||||
const resourceData = useMemo(() => {
|
||||
const parsedDataOnTheFly = parseQortalLink(embedLink);
|
||||
if(parsedDataOnTheFly?.service && parsedDataOnTheFly?.name && parsedDataOnTheFly?.identifier){
|
||||
if (
|
||||
parsedDataOnTheFly?.service &&
|
||||
parsedDataOnTheFly?.name &&
|
||||
parsedDataOnTheFly?.identifier
|
||||
) {
|
||||
return {
|
||||
service : parsedDataOnTheFly?.service,
|
||||
service: parsedDataOnTheFly?.service,
|
||||
name: parsedDataOnTheFly?.name,
|
||||
identifier: parsedDataOnTheFly?.identifier,
|
||||
fileName: parsedDataOnTheFly?.fileName ? decodeURIComponent(parsedDataOnTheFly?.fileName) : null,
|
||||
mimeType: parsedDataOnTheFly?.mimeType ? decodeURIComponent(parsedDataOnTheFly?.mimeType) : null,
|
||||
key: parsedDataOnTheFly?.key ? decodeURIComponent(parsedDataOnTheFly?.key) : null,
|
||||
}
|
||||
fileName: parsedDataOnTheFly?.fileName
|
||||
? decodeURIComponent(parsedDataOnTheFly?.fileName)
|
||||
: null,
|
||||
mimeType: parsedDataOnTheFly?.mimeType
|
||||
? decodeURIComponent(parsedDataOnTheFly?.mimeType)
|
||||
: null,
|
||||
key: parsedDataOnTheFly?.key
|
||||
? decodeURIComponent(parsedDataOnTheFly?.key)
|
||||
: null,
|
||||
};
|
||||
} else {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}, [embedLink])
|
||||
}, [embedLink]);
|
||||
|
||||
const keyIdentifier = useMemo(()=> {
|
||||
|
||||
if(resourceData){
|
||||
return `${resourceData.service}-${resourceData.name}-${resourceData.identifier}`
|
||||
const keyIdentifier = useMemo(() => {
|
||||
if (resourceData) {
|
||||
return `${resourceData.service}-${resourceData.name}-${resourceData.identifier}`;
|
||||
} else {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
}, [resourceData])
|
||||
}, [resourceData]);
|
||||
const blobUrl = useRecoilValue(blobKeySelector(keyIdentifier));
|
||||
|
||||
const handlePoll = async (parsedData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setErrorMsg("");
|
||||
setType("POLL");
|
||||
setErrorMsg('');
|
||||
setType('POLL');
|
||||
if (!parsedData?.name)
|
||||
throw new Error("Invalid poll embed link. Missing name.");
|
||||
throw new Error('Invalid poll embed link. Missing name.');
|
||||
const pollRes = await getPoll(parsedData.name);
|
||||
setPoll(pollRes);
|
||||
|
||||
} catch (error) {
|
||||
setErrorMsg(error?.message || "Invalid embed link");
|
||||
setErrorMsg(error?.message || 'Invalid embed link');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -106,8 +118,8 @@ export const Embed = ({ embedLink }) => {
|
||||
|
||||
const getImage = async ({ identifier, name, service }, key, parsedData) => {
|
||||
try {
|
||||
if(blobUrl?.blobUrl){
|
||||
return blobUrl?.blobUrl
|
||||
if (blobUrl?.blobUrl) {
|
||||
return blobUrl?.blobUrl;
|
||||
}
|
||||
let numberOfTries = 0;
|
||||
let imageFinalUrl = null;
|
||||
@ -116,76 +128,76 @@ export const Embed = ({ embedLink }) => {
|
||||
const urlStatus = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
|
||||
|
||||
const responseStatus = await fetch(urlStatus, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = await responseStatus.json();
|
||||
if (responseData?.status === "READY") {
|
||||
if (responseData?.status === 'READY') {
|
||||
if (parsedData?.encryptionType) {
|
||||
const urlData = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?encoding=base64`;
|
||||
|
||||
const responseData = await fetch(urlData, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await responseData.text();
|
||||
if (data) {
|
||||
let decryptedData
|
||||
let decryptedData;
|
||||
try {
|
||||
if(key && encryptionType === 'private'){
|
||||
if (key && encryptionType === 'private') {
|
||||
decryptedData = await window.sendMessage(
|
||||
"DECRYPT_DATA_WITH_SHARING_KEY",
|
||||
|
||||
{
|
||||
encryptedData: data,
|
||||
'DECRYPT_DATA_WITH_SHARING_KEY',
|
||||
|
||||
{
|
||||
encryptedData: data,
|
||||
key: decodeURIComponent(key),
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
if(encryptionType === 'group'){
|
||||
|
||||
if (encryptionType === 'group') {
|
||||
decryptedData = await window.sendMessage(
|
||||
"DECRYPT_QORTAL_GROUP_DATA",
|
||||
|
||||
{
|
||||
data64: data,
|
||||
groupId: selectedGroupId,
|
||||
}
|
||||
|
||||
);
|
||||
'DECRYPT_QORTAL_GROUP_DATA',
|
||||
|
||||
}
|
||||
{
|
||||
data64: data,
|
||||
groupId: selectedGroupId,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Unable to decrypt')
|
||||
throw new Error('Unable to decrypt');
|
||||
}
|
||||
|
||||
if (!decryptedData || decryptedData?.error) throw new Error("Could not decrypt data");
|
||||
imageFinalUrl = base64ToBlobUrl(decryptedData, parsedData?.mimeType ? decodeURIComponent(parsedData?.mimeType) : undefined)
|
||||
setBlobs((prev=> {
|
||||
|
||||
if (!decryptedData || decryptedData?.error)
|
||||
throw new Error('Could not decrypt data');
|
||||
imageFinalUrl = base64ToBlobUrl(
|
||||
decryptedData,
|
||||
parsedData?.mimeType
|
||||
? decodeURIComponent(parsedData?.mimeType)
|
||||
: undefined
|
||||
);
|
||||
setBlobs((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[`${service}-${name}-${identifier}`]: {
|
||||
blobUrl: imageFinalUrl,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}
|
||||
}))
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
throw new Error('No data for image')
|
||||
throw new Error('No data for image');
|
||||
}
|
||||
|
||||
} else {
|
||||
imageFinalUrl = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?async=true`;
|
||||
|
||||
// If parsedData is used here, it must be defined somewhere
|
||||
|
||||
}
|
||||
imageFinalUrl = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?async=true`;
|
||||
|
||||
// If parsedData is used here, it must be defined somewhere
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -203,18 +215,19 @@ export const Embed = ({ embedLink }) => {
|
||||
}
|
||||
|
||||
if (imageFinalUrl) {
|
||||
|
||||
return imageFinalUrl;
|
||||
} else {
|
||||
setErrorMsg(
|
||||
"Unable to download IMAGE. Please try again later by clicking the refresh button"
|
||||
'Unable to download IMAGE. Please try again later by clicking the refresh button'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching image:", error);
|
||||
console.error('Error fetching image:', error);
|
||||
setErrorMsg(
|
||||
error?.error || error?.message || "An unexpected error occurred while trying to download the image"
|
||||
error?.error ||
|
||||
error?.message ||
|
||||
'An unexpected error occurred while trying to download the image'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@ -223,25 +236,27 @@ export const Embed = ({ embedLink }) => {
|
||||
const handleImage = async (parsedData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setErrorMsg("");
|
||||
setErrorMsg('');
|
||||
if (!parsedData?.name || !parsedData?.service || !parsedData?.identifier)
|
||||
throw new Error("Invalid image embed link. Missing param.");
|
||||
let image = await getImage({
|
||||
name: parsedData.name,
|
||||
service: parsedData.service,
|
||||
identifier: parsedData?.identifier,
|
||||
}, parsedData?.key, parsedData);
|
||||
|
||||
setImageUrl(image);
|
||||
throw new Error('Invalid image embed link. Missing param.');
|
||||
let image = await getImage(
|
||||
{
|
||||
name: parsedData.name,
|
||||
service: parsedData.service,
|
||||
identifier: parsedData?.identifier,
|
||||
},
|
||||
parsedData?.key,
|
||||
parsedData
|
||||
);
|
||||
|
||||
setImageUrl(image);
|
||||
} catch (error) {
|
||||
setErrorMsg(error?.message || "Invalid embed link");
|
||||
setErrorMsg(error?.message || 'Invalid embed link');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleLink = () => {
|
||||
try {
|
||||
const parsedData = parseQortalLink(embedLink);
|
||||
@ -254,28 +269,26 @@ export const Embed = ({ embedLink }) => {
|
||||
setExternal(res);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
} catch (error) {}
|
||||
switch (type) {
|
||||
case "POLL":
|
||||
case 'POLL':
|
||||
{
|
||||
handlePoll(parsedData);
|
||||
}
|
||||
break;
|
||||
case "IMAGE":
|
||||
setType("IMAGE");
|
||||
case 'IMAGE':
|
||||
setType('IMAGE');
|
||||
|
||||
break;
|
||||
case 'ATTACHMENT':
|
||||
setType('ATTACHMENT');
|
||||
|
||||
break;
|
||||
case "ATTACHMENT":
|
||||
setType("ATTACHMENT");
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMsg(error?.message || "Invalid embed link");
|
||||
setErrorMsg(error?.message || 'Invalid embed link');
|
||||
}
|
||||
};
|
||||
|
||||
@ -284,13 +297,13 @@ export const Embed = ({ embedLink }) => {
|
||||
const parsedData = parseQortalLink(embedLink);
|
||||
handleImage(parsedData);
|
||||
} catch (error) {
|
||||
setErrorMsg(error?.message || "Invalid embed link");
|
||||
setErrorMsg(error?.message || 'Invalid embed link');
|
||||
}
|
||||
};
|
||||
|
||||
const openExternal = () => {
|
||||
executeEvent("addTab", { data: external });
|
||||
executeEvent("open-apps-mode", {});
|
||||
executeEvent('addTab', { data: external });
|
||||
executeEvent('open-apps-mode', {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -299,8 +312,6 @@ export const Embed = ({ embedLink }) => {
|
||||
hasFetched.current = true;
|
||||
}, [embedLink]);
|
||||
|
||||
|
||||
|
||||
const resourceDetails = useRecoilValue(resourceKeySelector(keyIdentifier));
|
||||
|
||||
const { parsedType, encryptionType } = useMemo(() => {
|
||||
@ -312,15 +323,17 @@ export const Embed = ({ embedLink }) => {
|
||||
parsedType = parsedDataOnTheFly.type;
|
||||
}
|
||||
if (parsedDataOnTheFly?.encryptionType) {
|
||||
encryptionType = parsedDataOnTheFly?.encryptionType
|
||||
encryptionType = parsedDataOnTheFly?.encryptionType;
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return { parsedType, encryptionType };
|
||||
}, [embedLink]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{parsedType === "POLL" && (
|
||||
{parsedType === 'POLL' && (
|
||||
<PollCard
|
||||
poll={poll}
|
||||
refresh={handleLink}
|
||||
@ -332,7 +345,7 @@ export const Embed = ({ embedLink }) => {
|
||||
errorMsg={errorMsg}
|
||||
/>
|
||||
)}
|
||||
{parsedType === "IMAGE" && (
|
||||
{parsedType === 'IMAGE' && (
|
||||
<ImageCard
|
||||
image={imageUrl}
|
||||
owner={parsedData?.name}
|
||||
@ -349,8 +362,8 @@ export const Embed = ({ embedLink }) => {
|
||||
)}
|
||||
{parsedType === 'ATTACHMENT' && (
|
||||
<AttachmentCard
|
||||
resourceData={resourceData}
|
||||
resourceDetails={resourceDetails}
|
||||
resourceData={resourceData}
|
||||
resourceDetails={resourceDetails}
|
||||
owner={parsedData?.name}
|
||||
refresh={fetchImage}
|
||||
setInfoSnack={setInfoSnack}
|
||||
@ -373,11 +386,3 @@ export const Embed = ({ embedLink }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { MyContext } from "../../App";
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { MyContext } from '../../App';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@ -12,384 +12,389 @@ import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Divider,
|
||||
|
||||
} from "@mui/material";
|
||||
import { getNameInfo } from "../Group/Group";
|
||||
import PollIcon from "@mui/icons-material/Poll";
|
||||
import { getFee } from "../../background";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
|
||||
} from '@mui/material';
|
||||
import { getNameInfo } from '../Group/Group';
|
||||
import PollIcon from '@mui/icons-material/Poll';
|
||||
import { getFee } from '../../background';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
|
||||
export const PollCard = ({
|
||||
poll,
|
||||
setInfoSnack,
|
||||
setOpenSnack,
|
||||
refresh,
|
||||
openExternal,
|
||||
external,
|
||||
isLoadingParent,
|
||||
errorMsg,
|
||||
}) => {
|
||||
const [selectedOption, setSelectedOption] = useState("");
|
||||
const [ownerName, setOwnerName] = useState("");
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { show, userInfo } = useContext(MyContext);
|
||||
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
|
||||
const handleVote = async () => {
|
||||
const fee = await getFee("VOTE_ON_POLL");
|
||||
|
||||
await show({
|
||||
message: `Do you accept this VOTE_ON_POLL transaction? POLLS are public!`,
|
||||
publishFee: fee.fee + " QORT",
|
||||
});
|
||||
setIsLoadingSubmit(true);
|
||||
|
||||
window
|
||||
.sendMessage(
|
||||
"voteOnPoll",
|
||||
{
|
||||
pollName: poll?.info?.pollName,
|
||||
optionIndex: +selectedOption,
|
||||
},
|
||||
60000
|
||||
)
|
||||
.then((response) => {
|
||||
setIsLoadingSubmit(false);
|
||||
if (response.error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: response?.error || "Unable to vote.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
return;
|
||||
} else {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message:
|
||||
"Successfully voted. Please wait a couple minutes for the network to propogate the changes.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setIsLoadingSubmit(false);
|
||||
poll,
|
||||
setInfoSnack,
|
||||
setOpenSnack,
|
||||
refresh,
|
||||
openExternal,
|
||||
external,
|
||||
isLoadingParent,
|
||||
errorMsg,
|
||||
}) => {
|
||||
const [selectedOption, setSelectedOption] = useState('');
|
||||
const [ownerName, setOwnerName] = useState('');
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { show, userInfo } = useContext(MyContext);
|
||||
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
|
||||
const handleVote = async () => {
|
||||
const fee = await getFee('VOTE_ON_POLL');
|
||||
|
||||
await show({
|
||||
message: `Do you accept this VOTE_ON_POLL transaction? POLLS are public!`,
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoadingSubmit(true);
|
||||
|
||||
window
|
||||
.sendMessage(
|
||||
'voteOnPoll',
|
||||
{
|
||||
pollName: poll?.info?.pollName,
|
||||
optionIndex: +selectedOption,
|
||||
},
|
||||
60000
|
||||
)
|
||||
.then((response) => {
|
||||
setIsLoadingSubmit(false);
|
||||
if (response.error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to vote.",
|
||||
type: 'error',
|
||||
message: response?.error || 'Unable to vote.',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
return;
|
||||
} else {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully voted. Please wait a couple minutes for the network to propogate the changes.',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
});
|
||||
};
|
||||
|
||||
const getName = async (owner) => {
|
||||
try {
|
||||
const res = await getNameInfo(owner);
|
||||
if (res) {
|
||||
setOwnerName(res);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (poll?.info?.owner) {
|
||||
getName(poll.info.owner);
|
||||
})
|
||||
.catch((error) => {
|
||||
setIsLoadingSubmit(false);
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to vote.',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
});
|
||||
};
|
||||
|
||||
const getName = async (owner) => {
|
||||
try {
|
||||
const res = await getNameInfo(owner);
|
||||
if (res) {
|
||||
setOwnerName(res);
|
||||
}
|
||||
}, [poll?.info?.owner]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (poll?.info?.owner) {
|
||||
getName(poll.info.owner);
|
||||
}
|
||||
}, [poll?.info?.owner]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: '#1F2023',
|
||||
height: isOpen ? 'auto' : '150px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#1F2023",
|
||||
height: isOpen ? "auto" : "150px",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '16px 16px 0px 16px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "16px 16px 0px 16px",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<PollIcon
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<PollIcon
|
||||
/>
|
||||
<Typography>POLL embed</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase>
|
||||
<RefreshIcon
|
||||
onClick={refresh}
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: '24px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
<Typography>POLL embed</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
</ButtonBase>
|
||||
{external && (
|
||||
<ButtonBase>
|
||||
<RefreshIcon
|
||||
onClick={refresh}
|
||||
<OpenInNewIcon
|
||||
onClick={openExternal}
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
color: "white",
|
||||
fontSize: '24px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
{external && (
|
||||
<ButtonBase>
|
||||
<OpenInNewIcon
|
||||
onClick={openExternal}
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
padding: '8px 16px 8px 16px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
padding: "8px 16px 8px 16px",
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
Created by {ownerName || poll?.info?.owner}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ borderColor: 'rgb(255 255 255 / 10%)' }} />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{!isOpen && !errorMsg && (
|
||||
<>
|
||||
<Spacer height="5px" />
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: 'var(--green)',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
Show poll
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{isLoadingParent && isOpen && (
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
Created by {ownerName || poll?.info?.owner}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ borderColor: "rgb(255 255 255 / 10%)" }} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{!isOpen && !errorMsg && (
|
||||
<>
|
||||
<Spacer height="5px" />
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "var(--green)",
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
Show poll
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{isLoadingParent && isOpen && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<CustomLoader />{" "}
|
||||
</Box>
|
||||
)}
|
||||
{errorMsg && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
color: "var(--danger)",
|
||||
}}
|
||||
>
|
||||
{errorMsg}
|
||||
</Typography>{" "}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: isOpen ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<CardHeader
|
||||
title={poll?.info?.pollName}
|
||||
subheader={poll?.info?.description}
|
||||
{' '}
|
||||
<CustomLoader />{' '}
|
||||
</Box>
|
||||
)}
|
||||
{errorMsg && (
|
||||
<Box
|
||||
sx={{
|
||||
"& .MuiCardHeader-title": {
|
||||
fontSize: "18px", // Custom font size for title
|
||||
},
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
<CardContent>
|
||||
>
|
||||
{' '}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
fontSize: '14px',
|
||||
color: 'var(--danger)',
|
||||
}}
|
||||
>
|
||||
Options
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
value={selectedOption}
|
||||
onChange={(e) => setSelectedOption(e.target.value)}
|
||||
>
|
||||
{poll?.info?.pollOptions?.map((option, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={index}
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: "white", // Unchecked color
|
||||
"&.Mui-checked": {
|
||||
color: "var(--green)", // Checked color
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={option?.optionName}
|
||||
sx={{
|
||||
"& .MuiFormControlLabel-label": {
|
||||
fontSize: "14px",
|
||||
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!selectedOption || isLoadingSubmit}
|
||||
onClick={handleVote}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
<Typography
|
||||
{errorMsg}
|
||||
</Typography>{' '}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: isOpen ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
<CardHeader
|
||||
title={poll?.info?.pollName}
|
||||
subheader={poll?.info?.description}
|
||||
sx={{
|
||||
'& .MuiCardHeader-title': {
|
||||
fontSize: '18px', // Custom font size for title
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '18px',
|
||||
}}
|
||||
>
|
||||
Options
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
value={selectedOption}
|
||||
onChange={(e) => setSelectedOption(e.target.value)}
|
||||
>
|
||||
{poll?.info?.pollOptions?.map((option, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={index}
|
||||
control={
|
||||
<Radio
|
||||
sx={{
|
||||
color: 'white', // Unchecked color
|
||||
'&.Mui-checked': {
|
||||
color: 'var(--green)', // Checked color
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={option?.optionName}
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontStyle: "italic",
|
||||
'& .MuiFormControlLabel-label': {
|
||||
fontSize: '14px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!selectedOption || isLoadingSubmit}
|
||||
onClick={handleVote}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
fontStyle: 'italic',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
{`${poll?.votes?.totalVotes} ${
|
||||
poll?.votes?.totalVotes === 1 ? ' vote' : ' votes'
|
||||
}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
visibility: poll?.votes?.votes?.find(
|
||||
(item) => item?.voterPublicKey === userInfo?.publicKey
|
||||
)
|
||||
? 'visible'
|
||||
: 'hidden',
|
||||
}}
|
||||
>
|
||||
You've already voted.
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
{isLoadingSubmit && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
Is processing transaction, please wait...
|
||||
</Typography>
|
||||
)}
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setShowResults((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
{showResults ? 'hide ' : 'show '} results
|
||||
</ButtonBase>
|
||||
</CardContent>
|
||||
{showResults && <PollResults votes={poll?.votes} />}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const PollResults = ({ votes }) => {
|
||||
const maxVotes = Math.max(
|
||||
...votes?.voteCounts?.map((option) => option.voteCount)
|
||||
);
|
||||
const options = votes?.voteCounts;
|
||||
return (
|
||||
<Box sx={{ width: '100%', p: 2 }}>
|
||||
{options
|
||||
.sort((a, b) => b.voteCount - a.voteCount) // Sort options by votes (highest first)
|
||||
.map((option, index) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: index === 0 ? 'bold' : 'normal',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
{`${poll?.votes?.totalVotes} ${
|
||||
poll?.votes?.totalVotes === 1 ? " vote" : " votes"
|
||||
}`}
|
||||
{`${index + 1}. ${option.optionName}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: index === 0 ? 'bold' : 'normal',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
{option.voteCount} votes
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
visibility: poll?.votes?.votes?.find(
|
||||
(item) => item?.voterPublicKey === userInfo?.publicKey
|
||||
)
|
||||
? "visible"
|
||||
: "hidden",
|
||||
mt: 1,
|
||||
height: 10,
|
||||
backgroundColor: '#e0e0e0',
|
||||
borderRadius: 5,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
You've already voted.
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
{isLoadingSubmit && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
}}
|
||||
>
|
||||
Is processing transaction, please wait...
|
||||
</Typography>
|
||||
)}
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setShowResults((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
{showResults ? "hide " : "show "} results
|
||||
</ButtonBase>
|
||||
</CardContent>
|
||||
{showResults && <PollResults votes={poll?.votes} />}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const PollResults = ({ votes }) => {
|
||||
const maxVotes = Math.max(
|
||||
...votes?.voteCounts?.map((option) => option.voteCount)
|
||||
);
|
||||
const options = votes?.voteCounts;
|
||||
return (
|
||||
<Box sx={{ width: "100%", p: 2 }}>
|
||||
{options
|
||||
.sort((a, b) => b.voteCount - a.voteCount) // Sort options by votes (highest first)
|
||||
.map((option, index) => (
|
||||
<Box key={index} sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ fontWeight: index === 0 ? "bold" : "normal" , fontSize: "14px"}}
|
||||
>
|
||||
{`${index + 1}. ${option.optionName}`}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ fontWeight: index === 0 ? "bold" : "normal" , fontSize: "14px"}}
|
||||
>
|
||||
{option.voteCount} votes
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 1,
|
||||
height: 10,
|
||||
backgroundColor: "#e0e0e0",
|
||||
borderRadius: 5,
|
||||
overflow: "hidden",
|
||||
width: `${(option.voteCount / maxVotes) * 100}%`,
|
||||
height: '100%',
|
||||
backgroundColor: index === 0 ? '#3f51b5' : '#f50057',
|
||||
transition: 'width 0.3s ease-in-out',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: `${(option.voteCount / maxVotes) * 100}%`,
|
||||
height: "100%",
|
||||
backgroundColor: index === 0 ? "#3f51b5" : "#f50057",
|
||||
transition: "width 0.3s ease-in-out",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +1,15 @@
|
||||
import * as React from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import Slide from "@mui/material/Slide";
|
||||
import { TransitionProps } from "@mui/material/transitions";
|
||||
import * as React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ExpandLess from '@mui/icons-material/ExpandLess';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
@ -24,23 +20,23 @@ import {
|
||||
Tab,
|
||||
Tabs,
|
||||
styled,
|
||||
} from "@mui/material";
|
||||
import { AddGroupList } from "./AddGroupList";
|
||||
import { UserListOfInvites } from "./UserListOfInvites";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { getFee } from "../../background";
|
||||
import { MyContext, isMobile } from "../../App";
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { AddGroupList } from './AddGroupList';
|
||||
import { UserListOfInvites } from './UserListOfInvites';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { getFee } from '../../background';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||
|
||||
export const Label = styled("label")(
|
||||
({ theme }) => `
|
||||
export const Label = styled('label')`
|
||||
display: block;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 400;
|
||||
`
|
||||
);
|
||||
margin-bottom: 4px;
|
||||
`;
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
props: TransitionProps & {
|
||||
children: React.ReactElement;
|
||||
@ -51,17 +47,15 @@ const Transition = React.forwardRef(function Transition(
|
||||
});
|
||||
|
||||
export const AddGroup = ({ address, open, setOpen }) => {
|
||||
const {show, setTxList} = React.useContext(MyContext)
|
||||
|
||||
const [tab, setTab] = React.useState("create");
|
||||
const { show, setTxList } = React.useContext(MyContext);
|
||||
const [tab, setTab] = React.useState('create');
|
||||
const [openAdvance, setOpenAdvance] = React.useState(false);
|
||||
|
||||
const [name, setName] = React.useState("");
|
||||
const [description, setDescription] = React.useState("");
|
||||
const [groupType, setGroupType] = React.useState("1");
|
||||
const [approvalThreshold, setApprovalThreshold] = React.useState("40");
|
||||
const [minBlock, setMinBlock] = React.useState("5");
|
||||
const [maxBlock, setMaxBlock] = React.useState("21600");
|
||||
const [name, setName] = React.useState('');
|
||||
const [description, setDescription] = React.useState('');
|
||||
const [groupType, setGroupType] = React.useState('1');
|
||||
const [approvalThreshold, setApprovalThreshold] = React.useState('40');
|
||||
const [minBlock, setMinBlock] = React.useState('5');
|
||||
const [maxBlock, setMaxBlock] = React.useState('21600');
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
@ -69,6 +63,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
@ -89,58 +84,59 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
setMaxBlock(event.target.value as string);
|
||||
};
|
||||
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const handleCreateGroup = async () => {
|
||||
try {
|
||||
if(!name) throw new Error('Please provide a name')
|
||||
if(!description) throw new Error('Please provide a description')
|
||||
if (!name) throw new Error('Please provide a name');
|
||||
if (!description) throw new Error('Please provide a description');
|
||||
|
||||
const fee = await getFee('CREATE_GROUP')
|
||||
const fee = await getFee('CREATE_GROUP');
|
||||
await show({
|
||||
message: "Would you like to perform an CREATE_GROUP transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
message: 'Would you like to perform an CREATE_GROUP transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("createGroup", {
|
||||
groupName: name,
|
||||
groupDescription: description,
|
||||
groupType: +groupType,
|
||||
groupApprovalThreshold: +approvalThreshold,
|
||||
minBlock: +minBlock,
|
||||
maxBlock: +maxBlock,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully created group. It may take a couple of minutes for the changes to propagate",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-group',
|
||||
label: `Created group ${name}: awaiting confirmation`,
|
||||
labelDone: `Created group ${name}: success!`,
|
||||
done: false,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
rej({ message: response.error });
|
||||
})
|
||||
.catch((error) => {
|
||||
rej({ message: error.message || "An error occurred" });
|
||||
});
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('createGroup', {
|
||||
groupName: name,
|
||||
groupDescription: description,
|
||||
groupType: +groupType,
|
||||
groupApprovalThreshold: +approvalThreshold,
|
||||
minBlock: +minBlock,
|
||||
maxBlock: +maxBlock,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created group. It may take a couple of minutes for the changes to propagate',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-group',
|
||||
label: `Created group ${name}: awaiting confirmation`,
|
||||
labelDone: `Created group ${name}: success!`,
|
||||
done: false,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
rej({ message: response.error });
|
||||
})
|
||||
.catch((error) => {
|
||||
rej({ message: error.message || 'An error occurred' });
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message: error?.message,
|
||||
});
|
||||
setOpenSnack(true);
|
||||
@ -166,20 +162,22 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const openGroupInvitesRequestFunc = ()=> {
|
||||
setValue(2)
|
||||
}
|
||||
const openGroupInvitesRequestFunc = () => {
|
||||
setValue(2);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
subscribeToEvent("openGroupInvitesRequest", openGroupInvitesRequestFunc);
|
||||
subscribeToEvent('openGroupInvitesRequest', openGroupInvitesRequestFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("openGroupInvitesRequest", openGroupInvitesRequestFunc);
|
||||
unsubscribeFromEvent(
|
||||
'openGroupInvitesRequest',
|
||||
openGroupInvitesRequestFunc
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -191,17 +189,22 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
onClose={handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar sx={{ position: "relative", bgcolor: "#232428" }}>
|
||||
<AppBar
|
||||
sx={{
|
||||
position: 'relative',
|
||||
bgcolor: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
|
||||
Group Mgmt
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
|
||||
Group Management
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={handleClose}
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
edge="start"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
@ -213,275 +216,292 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
</AppBar>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: "#27282c",
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
color: "white",
|
||||
bgcolor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
display: 'flex'
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
|
||||
scrollButtons="auto"
|
||||
allowScrollButtonsMobile
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="Create Group"
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Find Group"
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Group Invites"
|
||||
{...a11yProps(2)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
<Box
|
||||
sx={{ borderBottom: 1, borderColor: theme.palette.text.secondary }}
|
||||
>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
|
||||
scrollButtons="auto"
|
||||
allowScrollButtonsMobile
|
||||
sx={{
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="Create Group"
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Find Group"
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Group Invites"
|
||||
{...a11yProps(2)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
|
||||
{value === 0 && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
padding: '25px'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
maxWidth: "500px",
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
maxWidth: '500px',
|
||||
}}
|
||||
>
|
||||
<Label>Name of group</Label>
|
||||
<Input
|
||||
placeholder="Name of group"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Label>Description of group</Label>
|
||||
|
||||
<Input
|
||||
placeholder="Description of group"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Label>Group type</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={groupType}
|
||||
label="Group Type"
|
||||
onChange={handleChangeGroupType}
|
||||
>
|
||||
<MenuItem value={1}>Open (public)</MenuItem>
|
||||
<MenuItem value={0}>
|
||||
Closed (private) - users need permission to join
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => setOpenAdvance((prev) => !prev)}
|
||||
>
|
||||
<Typography>Advanced options</Typography>
|
||||
|
||||
{openAdvance ? <ExpandLess /> : <ExpandMore />}
|
||||
</Box>
|
||||
<Collapse in={openAdvance} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Group Approval Threshold (number / percentage of Admins that
|
||||
must approve a transaction)
|
||||
</Label>
|
||||
<Label>Name of group</Label>
|
||||
<Input
|
||||
placeholder="Name of group"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Description of group</Label>
|
||||
|
||||
<Input
|
||||
placeholder="Description of group"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Group type</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={approvalThreshold}
|
||||
label="Group Approval Threshold"
|
||||
onChange={handleChangeApprovalThreshold}
|
||||
value={groupType}
|
||||
label="Group Type"
|
||||
onChange={handleChangeGroupType}
|
||||
>
|
||||
<MenuItem value={0}>NONE</MenuItem>
|
||||
<MenuItem value={1}>ONE </MenuItem>
|
||||
|
||||
<MenuItem value={20}>20% </MenuItem>
|
||||
<MenuItem value={40}>40% </MenuItem>
|
||||
<MenuItem value={60}>60% </MenuItem>
|
||||
<MenuItem value={80}>80% </MenuItem>
|
||||
<MenuItem value={100}>100% </MenuItem>
|
||||
<MenuItem value={1}>Open (public)</MenuItem>
|
||||
<MenuItem value={0}>
|
||||
Closed (private) - users need permission to join
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
gap: '15px',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => setOpenAdvance((prev) => !prev)}
|
||||
>
|
||||
<Label>
|
||||
Minimum Block delay for Group Transaction Approvals
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={minBlock}
|
||||
label="Minimum Block delay"
|
||||
onChange={handleChangeMinBlock}
|
||||
>
|
||||
<MenuItem value={5}>5 minutes</MenuItem>
|
||||
<MenuItem value={10}>10 minutes</MenuItem>
|
||||
<MenuItem value={30}>30 minutes</MenuItem>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
</Select>
|
||||
<Typography>Advanced options</Typography>
|
||||
|
||||
{openAdvance ? <ExpandLess /> : <ExpandMore />}
|
||||
</Box>
|
||||
<Collapse in={openAdvance} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Group Approval Threshold (number / percentage of Admins
|
||||
that must approve a transaction)
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={approvalThreshold}
|
||||
label="Group Approval Threshold"
|
||||
onChange={handleChangeApprovalThreshold}
|
||||
>
|
||||
<MenuItem value={0}>NONE</MenuItem>
|
||||
<MenuItem value={1}>ONE </MenuItem>
|
||||
|
||||
<MenuItem value={20}>20% </MenuItem>
|
||||
<MenuItem value={40}>40% </MenuItem>
|
||||
<MenuItem value={60}>60% </MenuItem>
|
||||
<MenuItem value={80}>80% </MenuItem>
|
||||
<MenuItem value={100}>100% </MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Minimum Block delay for Group Transaction Approvals
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={minBlock}
|
||||
label="Minimum Block delay"
|
||||
onChange={handleChangeMinBlock}
|
||||
>
|
||||
<MenuItem value={5}>5 minutes</MenuItem>
|
||||
<MenuItem value={10}>10 minutes</MenuItem>
|
||||
<MenuItem value={30}>30 minutes</MenuItem>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Maximum Block delay for Group Transaction Approvals
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={maxBlock}
|
||||
label="Maximum Block delay"
|
||||
onChange={handleChangeMaxBlock}
|
||||
>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
<MenuItem value={14400}>10 days</MenuItem>
|
||||
<MenuItem value={21600}>15 days</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Maximum Block delay for Group Transaction Approvals
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={maxBlock}
|
||||
label="Maximum Block delay"
|
||||
onChange={handleChangeMaxBlock}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreateGroup}
|
||||
>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
<MenuItem value={14400}>10 days</MenuItem>
|
||||
<MenuItem value={21600}>15 days</MenuItem>
|
||||
</Select>
|
||||
Create Group
|
||||
</Button>
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreateGroup}
|
||||
>
|
||||
Create Group
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{value === 1 && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
display: 'flex'
|
||||
}}>
|
||||
<AddGroupList setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AddGroupList
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
)}
|
||||
|
||||
{value === 2 && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
display: 'flex'
|
||||
}}>
|
||||
<UserListOfInvites myAddress={address} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
{value === 2 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<UserListOfInvites
|
||||
myAddress={address}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
Popover,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
@ -15,20 +15,20 @@ import React, {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
} from 'react';
|
||||
import {
|
||||
AutoSizer,
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
List,
|
||||
} from "react-virtualized";
|
||||
import _ from "lodash";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { getBaseApi, getFee } from "../../background";
|
||||
} from 'react-virtualized';
|
||||
import _ from 'lodash';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 50,
|
||||
@ -41,7 +41,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
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 listRef = useRef();
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [filteredItems, setFilteredItems] = useState(groups);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@ -72,9 +72,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
|
||||
const getGroups = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/groups/?limit=0`
|
||||
);
|
||||
const response = await fetch(`${getBaseApiReact()}/groups/?limit=0`);
|
||||
const groupData = await response.json();
|
||||
const filteredGroup = groupData.filter(
|
||||
(item) => !memberGroups.find((group) => group.groupId === item.groupId)
|
||||
@ -103,23 +101,25 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
const handleJoinGroup = async (group, isOpen) => {
|
||||
try {
|
||||
const groupId = group.groupId;
|
||||
const fee = await getFee('JOIN_GROUP')
|
||||
await show({
|
||||
message: "Would you like to perform an JOIN_GROUP transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
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,
|
||||
})
|
||||
window
|
||||
.sendMessage('joinGroup', {
|
||||
groupId,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully requested to join group. It may take a couple of minutes for the changes to propagate",
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
|
||||
});
|
||||
|
||||
|
||||
if (isOpen) {
|
||||
setTxList((prev) => [
|
||||
{
|
||||
@ -145,14 +145,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
res(response);
|
||||
return;
|
||||
} else {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message: response?.error,
|
||||
});
|
||||
setOpenSnack(true);
|
||||
@ -161,18 +161,18 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error.message || "An error occurred",
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
|
||||
});
|
||||
setIsLoading(false);
|
||||
} catch (error) {} finally {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -195,30 +195,30 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
anchorEl={popoverAnchor}
|
||||
onClose={handlePopoverClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
style={{ marginTop: "8px" }}
|
||||
style={{ marginTop: '8px' }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "325px",
|
||||
height: "250px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
padding: "10px",
|
||||
width: '325px',
|
||||
height: '250px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<Typography>Join {group?.groupName}</Typography>
|
||||
<Typography>
|
||||
{group?.isOpen === false &&
|
||||
"This is a closed/private group, so you will need to wait until an admin accepts your request"}
|
||||
'This is a closed/private group, so you will need to wait until an admin accepts your request'}
|
||||
</Typography>
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
@ -234,16 +234,20 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
{group?.isOpen === false && (
|
||||
<LockIcon sx={{
|
||||
color: 'var(--green)'
|
||||
}} />
|
||||
)}
|
||||
{group?.isOpen === true && (
|
||||
<NoEncryptionGmailerrorredIcon sx={{
|
||||
color: 'var(--danger)'
|
||||
}} />
|
||||
)}
|
||||
<Spacer width="15px" />
|
||||
<LockIcon
|
||||
sx={{
|
||||
color: 'var(--green)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{group?.isOpen === true && (
|
||||
<NoEncryptionGmailerrorredIcon
|
||||
sx={{
|
||||
color: 'var(--danger)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Spacer width="15px" />
|
||||
<ListItemText
|
||||
primary={group?.groupName}
|
||||
secondary={group?.description}
|
||||
@ -257,11 +261,13 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<p>Groups list</p>
|
||||
<TextField
|
||||
label="Search for Groups"
|
||||
@ -272,10 +278,10 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
|
@ -8,64 +8,78 @@ import {
|
||||
DialogTitle,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { getBaseApiReact, MyContext } from "../../App";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||
import { validateAddress } from "../../utils/validateAddress";
|
||||
import { getNameInfo, requestQueueMemberNames } from "./Group";
|
||||
import { useModal } from "../../common/useModal";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { isOpenBlockedModalAtom } from "../../atoms/global";
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { getBaseApiReact, MyContext } from '../../App';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../utils/events';
|
||||
import { validateAddress } from '../../utils/validateAddress';
|
||||
import { getNameInfo, requestQueueMemberNames } from './Group';
|
||||
import { useModal } from '../../common/useModal';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isOpenBlockedModalAtom } from '../../atoms/global';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
export const BlockedUsersModal = () => {
|
||||
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(isOpenBlockedModalAtom)
|
||||
const theme = useTheme();
|
||||
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(
|
||||
isOpenBlockedModalAtom
|
||||
);
|
||||
const [hasChanged, setHasChanged] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
const [addressesWithNames, setAddressesWithNames] = useState({})
|
||||
const [value, setValue] = useState('');
|
||||
const [addressesWithNames, setAddressesWithNames] = useState({});
|
||||
const { isShow, onCancel, onOk, show, message } = useModal();
|
||||
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
|
||||
useContext(MyContext);
|
||||
const {
|
||||
getAllBlockedUsers,
|
||||
removeBlockFromList,
|
||||
addToBlockList,
|
||||
setOpenSnackGlobal,
|
||||
setInfoSnackCustom,
|
||||
} = useContext(MyContext);
|
||||
|
||||
const [blockedUsers, setBlockedUsers] = useState({
|
||||
addresses: {},
|
||||
names: {},
|
||||
});
|
||||
|
||||
const fetchBlockedUsers = () => {
|
||||
setBlockedUsers(getAllBlockedUsers());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(!isOpenBlockedModal) return
|
||||
if (!isOpenBlockedModal) return;
|
||||
fetchBlockedUsers();
|
||||
}, [isOpenBlockedModal]);
|
||||
|
||||
const getNames = async () => {
|
||||
const getNames = async () => {
|
||||
// const validApi = await findUsableApi();
|
||||
const addresses = Object.keys(blockedUsers?.addresses)
|
||||
const addressNames = {}
|
||||
const addresses = Object.keys(blockedUsers?.addresses);
|
||||
const addressNames = {};
|
||||
|
||||
|
||||
const getMemNames = addresses.map(async (address) => {
|
||||
const name = await requestQueueMemberNames.enqueue(() => {
|
||||
return getNameInfo(address);
|
||||
});
|
||||
if (name) {
|
||||
addressNames[address] = name
|
||||
}
|
||||
|
||||
|
||||
const name = await requestQueueMemberNames.enqueue(() => {
|
||||
return getNameInfo(address);
|
||||
});
|
||||
if (name) {
|
||||
addressNames[address] = name;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
await Promise.all(getMemNames);
|
||||
|
||||
setAddressesWithNames(addressNames)
|
||||
|
||||
setAddressesWithNames(addressNames);
|
||||
};
|
||||
|
||||
const blockUser = async (e, user?: string) => {
|
||||
try {
|
||||
const valUser = user || value
|
||||
const valUser = user || value;
|
||||
if (!valUser) return;
|
||||
const isAddress = validateAddress(valUser);
|
||||
let userName = null;
|
||||
@ -80,62 +94,66 @@ export const BlockedUsersModal = () => {
|
||||
if (!isAddress) {
|
||||
const response = await fetch(`${getBaseApiReact()}/names/${valUser}`);
|
||||
const data = await response.json();
|
||||
if (!data?.owner) throw new Error("Name does not exist");
|
||||
if (!data?.owner) throw new Error('Name does not exist');
|
||||
if (data?.owner) {
|
||||
userAddress = data.owner;
|
||||
userName = valUser;
|
||||
}
|
||||
}
|
||||
if(!userName){
|
||||
if (!userName) {
|
||||
await addToBlockList(userAddress, null);
|
||||
fetchBlockedUsers();
|
||||
setHasChanged(true);
|
||||
executeEvent('updateChatMessagesWithBlocks', true)
|
||||
setValue('')
|
||||
return
|
||||
executeEvent('updateChatMessagesWithBlocks', true);
|
||||
setValue('');
|
||||
return;
|
||||
}
|
||||
const responseModal = await show({
|
||||
userName,
|
||||
userAddress,
|
||||
});
|
||||
if (responseModal === "both") {
|
||||
if (responseModal === 'both') {
|
||||
await addToBlockList(userAddress, userName);
|
||||
} else if (responseModal === "address") {
|
||||
} else if (responseModal === 'address') {
|
||||
await addToBlockList(userAddress, null);
|
||||
} else if (responseModal === "name") {
|
||||
} else if (responseModal === 'name') {
|
||||
await addToBlockList(null, userName);
|
||||
}
|
||||
fetchBlockedUsers();
|
||||
setHasChanged(true);
|
||||
setValue('')
|
||||
if(user){
|
||||
setIsOpenBlockedModal(false)
|
||||
setValue('');
|
||||
if (user) {
|
||||
setIsOpenBlockedModal(false);
|
||||
}
|
||||
if(responseModal === 'both' || responseModal === 'address'){
|
||||
executeEvent('updateChatMessagesWithBlocks', true)
|
||||
if (responseModal === 'both' || responseModal === 'address') {
|
||||
executeEvent('updateChatMessagesWithBlocks', true);
|
||||
}
|
||||
} catch (error) {
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to block user",
|
||||
});
|
||||
setInfoSnackCustom({
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to block user',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const blockUserFromOutsideModalFunc = (e) => {
|
||||
const user = e.detail?.user;
|
||||
setIsOpenBlockedModal(true)
|
||||
blockUser(null, user)
|
||||
const user = e.detail?.user;
|
||||
setIsOpenBlockedModal(true);
|
||||
blockUser(null, user);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent('blockUserFromOutside', blockUserFromOutsideModalFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent(
|
||||
'blockUserFromOutside',
|
||||
blockUserFromOutsideModalFunc
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
|
||||
};
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpenBlockedModal}
|
||||
@ -145,14 +163,14 @@ export const BlockedUsersModal = () => {
|
||||
<DialogTitle>Blocked Users</DialogTitle>
|
||||
<DialogContent
|
||||
sx={{
|
||||
padding: "20px",
|
||||
padding: '20px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
@ -180,16 +198,18 @@ export const BlockedUsersModal = () => {
|
||||
Blocked addresses- blocks processing of txs
|
||||
</DialogContentText>
|
||||
<Spacer height="10px" />
|
||||
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
|
||||
<Button variant="contained" size="small" onClick={getNames}>
|
||||
Fetch names
|
||||
</Button>
|
||||
<Spacer height="10px" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
{Object.entries(blockedUsers?.addresses || {})?.map(
|
||||
@ -197,11 +217,11 @@ export const BlockedUsersModal = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography>{addressesWithNames[key] || key}</Typography>
|
||||
@ -215,7 +235,7 @@ export const BlockedUsersModal = () => {
|
||||
try {
|
||||
await removeBlockFromList(key, undefined);
|
||||
setHasChanged(true);
|
||||
setValue("");
|
||||
setValue('');
|
||||
fetchBlockedUsers();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -241,20 +261,20 @@ export const BlockedUsersModal = () => {
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography>{key}</Typography>
|
||||
@ -284,20 +304,20 @@ export const BlockedUsersModal = () => {
|
||||
<DialogActions>
|
||||
<Button
|
||||
sx={{
|
||||
backgroundColor: "var(--green)",
|
||||
color: "black",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--green)",
|
||||
color: "black",
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
if (hasChanged) {
|
||||
executeEvent("updateChatMessagesWithBlocks", true);
|
||||
executeEvent('updateChatMessagesWithBlocks', true);
|
||||
}
|
||||
setIsOpenBlockedModal(false);
|
||||
}}
|
||||
@ -312,28 +332,37 @@ export const BlockedUsersModal = () => {
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Decide what to block"}
|
||||
{'Decide what to block'}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Blocking {message?.userName || message?.userAddress}
|
||||
</DialogContentText>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<InfoIcon sx={{
|
||||
color: 'fff'
|
||||
}}/> <Typography>Choose "block txs" or "all" to block chat messages </Typography>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
marginTop: '20px',
|
||||
}}
|
||||
>
|
||||
<InfoIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>{' '}
|
||||
<Typography>
|
||||
Choose "block txs" or "all" to block chat messages{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
onOk("address");
|
||||
onOk('address');
|
||||
}}
|
||||
>
|
||||
Block txs
|
||||
@ -341,7 +370,7 @@ export const BlockedUsersModal = () => {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
onOk("name");
|
||||
onOk('name');
|
||||
}}
|
||||
>
|
||||
Block QDN data
|
||||
@ -349,7 +378,7 @@ export const BlockedUsersModal = () => {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
onOk("both");
|
||||
onOk('both');
|
||||
}}
|
||||
>
|
||||
Block All
|
||||
|
@ -5,10 +5,10 @@ import React, {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Avatar, Box, Popover, Typography } from "@mui/material";
|
||||
} from 'react';
|
||||
import { Avatar, Box, Popover, Typography } from '@mui/material';
|
||||
// import { MAIL_SERVICE_TYPE, THREAD_SERVICE_TYPE } from "../../constants/mail";
|
||||
import { Thread } from "./Thread";
|
||||
import { Thread } from './Thread';
|
||||
import {
|
||||
AllThreadP,
|
||||
ArrowDownIcon,
|
||||
@ -38,61 +38,73 @@ import {
|
||||
ThreadSingleLastMessageP,
|
||||
ThreadSingleLastMessageSpanP,
|
||||
ThreadSingleTitle,
|
||||
} from "./Mail-styles";
|
||||
} from './Mail-styles';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import { Spacer } from "../../../common/Spacer";
|
||||
import { formatDate, formatTimestamp } from "../../../utils/time";
|
||||
import LazyLoad from "../../../common/LazyLoad";
|
||||
import { delay } from "../../../utils/helpers";
|
||||
import { NewThread } from "./NewThread";
|
||||
import { getBaseApi } from "../../../background";
|
||||
import { decryptPublishes, getTempPublish, handleUnencryptedPublishes } from "../../Chat/GroupAnnouncements";
|
||||
import CheckSVG from "../../../assets/svgs/Check.svg";
|
||||
import SortSVG from "../../../assets/svgs/Sort.svg";
|
||||
import ArrowDownSVG from "../../../assets/svgs/ArrowDown.svg";
|
||||
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar";
|
||||
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events";
|
||||
import { Spacer } from '../../../common/Spacer';
|
||||
import { formatDate, formatTimestamp } from '../../../utils/time';
|
||||
import LazyLoad from '../../../common/LazyLoad';
|
||||
import { delay } from '../../../utils/helpers';
|
||||
import { NewThread } from './NewThread';
|
||||
import { getBaseApi } from '../../../background';
|
||||
import {
|
||||
decryptPublishes,
|
||||
getTempPublish,
|
||||
handleUnencryptedPublishes,
|
||||
} from '../../Chat/GroupAnnouncements';
|
||||
import CheckSVG from '../../../assets/svgs/Check.svg';
|
||||
import SortSVG from '../../../assets/svgs/Sort.svg';
|
||||
import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg';
|
||||
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../../utils/events';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { getArbitraryEndpointReact, getBaseApiReact, isMobile } from "../../../App";
|
||||
import { WrapperUserAction } from "../../WrapperUserAction";
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from "../Group";
|
||||
const filterOptions = ["Recently active", "Newest", "Oldest"];
|
||||
import {
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
isMobile,
|
||||
} from '../../../App';
|
||||
import { WrapperUserAction } from '../../WrapperUserAction';
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
|
||||
const filterOptions = ['Recently active', 'Newest', 'Oldest'];
|
||||
|
||||
export const threadIdentifier = "DOCUMENT";
|
||||
export const threadIdentifier = 'DOCUMENT';
|
||||
export const GroupMail = ({
|
||||
selectedGroup,
|
||||
userInfo,
|
||||
getSecretKey,
|
||||
secretKey,
|
||||
defaultThread,
|
||||
defaultThread,
|
||||
setDefaultThread,
|
||||
hide,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
}) => {
|
||||
const [viewedThreads, setViewedThreads] = React.useState<any>({});
|
||||
const [filterMode, setFilterMode] = useState<string>("Recently active");
|
||||
const [filterMode, setFilterMode] = useState<string>('Recently active');
|
||||
const [currentThread, setCurrentThread] = React.useState(null);
|
||||
const [recentThreads, setRecentThreads] = useState<any[]>([]);
|
||||
const [allThreads, setAllThreads] = useState<any[]>([]);
|
||||
const [members, setMembers] = useState<any>(null);
|
||||
const [isOpenFilterList, setIsOpenFilterList] = useState<boolean>(false);
|
||||
const anchorElInstanceFilter = useRef<any>(null);
|
||||
const [tempPublishedList, setTempPublishedList] = useState([])
|
||||
const dataPublishes = useRef({})
|
||||
const [tempPublishedList, setTempPublishedList] = useState([]);
|
||||
const dataPublishes = useRef({});
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const groupIdRef = useRef<any>(null);
|
||||
const groupId = useMemo(() => {
|
||||
return selectedGroup?.groupId;
|
||||
}, [selectedGroup]);
|
||||
|
||||
useEffect(()=> {
|
||||
if(!groupId) return
|
||||
(async ()=> {
|
||||
const res = await getDataPublishesFunc(groupId, 'thread')
|
||||
dataPublishes.current = res || {}
|
||||
})()
|
||||
}, [groupId])
|
||||
useEffect(() => {
|
||||
if (!groupId) return;
|
||||
(async () => {
|
||||
const res = await getDataPublishesFunc(groupId, 'thread');
|
||||
dataPublishes.current = res || {};
|
||||
})();
|
||||
}, [groupId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupId !== groupIdRef?.current) {
|
||||
@ -103,55 +115,66 @@ export const GroupMail = ({
|
||||
}
|
||||
}, [groupId]);
|
||||
|
||||
const setTempData = async ()=> {
|
||||
const setTempData = async () => {
|
||||
try {
|
||||
const getTempAnnouncements = await getTempPublish()
|
||||
|
||||
if(getTempAnnouncements?.thread){
|
||||
let tempData = []
|
||||
Object.keys(getTempAnnouncements?.thread || {}).map((key)=> {
|
||||
const value = getTempAnnouncements?.thread[key]
|
||||
if(value?.data?.groupId === groupIdRef?.current){
|
||||
tempData.push(value.data)
|
||||
const getTempAnnouncements = await getTempPublish();
|
||||
|
||||
if (getTempAnnouncements?.thread) {
|
||||
let tempData = [];
|
||||
Object.keys(getTempAnnouncements?.thread || {}).map((key) => {
|
||||
const value = getTempAnnouncements?.thread[key];
|
||||
if (value?.data?.groupId === groupIdRef?.current) {
|
||||
tempData.push(value.data);
|
||||
}
|
||||
});
|
||||
setTempPublishedList(tempData);
|
||||
}
|
||||
|
||||
})
|
||||
setTempPublishedList(tempData)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const getEncryptedResource = async ({ name, identifier, resource }, isPrivate) => {
|
||||
let data = dataPublishes.current[`${name}-${identifier}`]
|
||||
if(!data || (data?.update || data?.created !== (resource?.updated || resource?.created))){
|
||||
const res = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
|
||||
);
|
||||
if(!res?.ok) return
|
||||
data = await res.text();
|
||||
await addDataPublishesFunc({...resource, data}, groupId, 'thread')
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const getEncryptedResource = async (
|
||||
{ name, identifier, resource },
|
||||
isPrivate
|
||||
) => {
|
||||
let data = dataPublishes.current[`${name}-${identifier}`];
|
||||
if (
|
||||
!data ||
|
||||
data?.update ||
|
||||
data?.created !== (resource?.updated || resource?.created)
|
||||
) {
|
||||
const res = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
|
||||
);
|
||||
if (!res?.ok) return;
|
||||
data = await res.text();
|
||||
await addDataPublishesFunc({ ...resource, data }, groupId, 'thread');
|
||||
} else {
|
||||
data = data.data
|
||||
data = data.data;
|
||||
}
|
||||
const response = isPrivate === false ? handleUnencryptedPublishes([data]) : await decryptPublishes([{ data }], secretKey);
|
||||
const response =
|
||||
isPrivate === false
|
||||
? handleUnencryptedPublishes([data])
|
||||
: await decryptPublishes([{ data }], secretKey);
|
||||
|
||||
const messageData = response[0];
|
||||
return messageData.decryptedData;
|
||||
};
|
||||
|
||||
const updateThreadActivity = async ({threadId, qortalName, groupId, thread}) => {
|
||||
const updateThreadActivity = async ({
|
||||
threadId,
|
||||
qortalName,
|
||||
groupId,
|
||||
thread,
|
||||
}) => {
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("updateThreadActivity", {
|
||||
threadId,
|
||||
qortalName,
|
||||
groupId,
|
||||
thread,
|
||||
})
|
||||
window
|
||||
.sendMessage('updateThreadActivity', {
|
||||
threadId,
|
||||
qortalName,
|
||||
groupId,
|
||||
thread,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
@ -160,13 +183,10 @@ export const GroupMail = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
@ -174,9 +194,9 @@ export const GroupMail = ({
|
||||
const getAllThreads = React.useCallback(
|
||||
async (groupId: string, mode: string, isInitial?: boolean) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setIsLoading(true);
|
||||
const offset = isInitial ? 0 : allThreads.length;
|
||||
const isReverse = mode === "Newest" ? true : false;
|
||||
const isReverse = mode === 'Newest' ? true : false;
|
||||
if (isInitial) {
|
||||
// dispatch(setIsLoadingCustom("Loading threads"));
|
||||
}
|
||||
@ -184,9 +204,9 @@ export const GroupMail = ({
|
||||
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=${20}&includemetadata=false&offset=${offset}&reverse=${isReverse}&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
@ -209,21 +229,26 @@ export const GroupMail = ({
|
||||
let threadRes = null;
|
||||
try {
|
||||
threadRes = await Promise.race([
|
||||
getEncryptedResource({
|
||||
name: message.name,
|
||||
identifier: message.identifier,
|
||||
resource: message
|
||||
}, isPrivate),
|
||||
getEncryptedResource(
|
||||
{
|
||||
name: message.name,
|
||||
identifier: message.identifier,
|
||||
resource: message,
|
||||
},
|
||||
isPrivate
|
||||
),
|
||||
delay(5000),
|
||||
]);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
if (threadRes?.title) {
|
||||
fullObject = {
|
||||
...message,
|
||||
threadData: threadRes,
|
||||
threadOwner: message?.name,
|
||||
threadId: message.identifier
|
||||
threadId: message.identifier,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -251,7 +276,7 @@ export const GroupMail = ({
|
||||
console.log({ error });
|
||||
} finally {
|
||||
if (isInitial) {
|
||||
setIsLoading(false)
|
||||
setIsLoading(false);
|
||||
// dispatch(setIsLoadingCustom(null));
|
||||
}
|
||||
}
|
||||
@ -261,21 +286,21 @@ export const GroupMail = ({
|
||||
const getMailMessages = React.useCallback(
|
||||
async (groupId: string, members: any) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setIsLoading(true);
|
||||
|
||||
const identifier = `thmsg-grp-${groupId}-thread-`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=100&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
const messagesForThread: any = {};
|
||||
for (const message of responseData) {
|
||||
let str = message.identifier;
|
||||
const parts = str.split("-");
|
||||
const parts = str.split('-');
|
||||
|
||||
// Get the second last element
|
||||
const secondLastId = parts[parts.length - 2];
|
||||
@ -295,16 +320,16 @@ export const GroupMail = ({
|
||||
})
|
||||
.sort((a, b) => b.created - a.created)
|
||||
.slice(0, 10);
|
||||
|
||||
|
||||
let fullThreadArray: any = [];
|
||||
const getMessageForThreads = newArray.map(async (message: any) => {
|
||||
try {
|
||||
const identifierQuery = message.threadId;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifierQuery}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
@ -324,11 +349,14 @@ export const GroupMail = ({
|
||||
fullThreadArray.push(fullObject);
|
||||
} else {
|
||||
let threadRes = await Promise.race([
|
||||
getEncryptedResource({
|
||||
name: thread.name,
|
||||
identifier: message.threadId,
|
||||
resource: thread
|
||||
}, isPrivate),
|
||||
getEncryptedResource(
|
||||
{
|
||||
name: thread.name,
|
||||
identifier: message.threadId,
|
||||
resource: thread,
|
||||
},
|
||||
isPrivate
|
||||
),
|
||||
delay(10000),
|
||||
]);
|
||||
if (threadRes?.title) {
|
||||
@ -353,7 +381,7 @@ export const GroupMail = ({
|
||||
setRecentThreads(sorted);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setIsLoading(false);
|
||||
// dispatch(setIsLoadingCustom(null));
|
||||
}
|
||||
},
|
||||
@ -361,7 +389,6 @@ export const GroupMail = ({
|
||||
);
|
||||
|
||||
const getMessages = React.useCallback(async () => {
|
||||
|
||||
// if ( !groupId || members?.length === 0) return;
|
||||
if (!groupId || isPrivate === null) return;
|
||||
|
||||
@ -371,23 +398,23 @@ export const GroupMail = ({
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const firstMount = useRef(false);
|
||||
const filterModeRef = useRef("");
|
||||
const filterModeRef = useRef('');
|
||||
|
||||
useEffect(() => {
|
||||
if(hide) return
|
||||
if (hide) return;
|
||||
if (filterModeRef.current !== filterMode) {
|
||||
firstMount.current = false;
|
||||
}
|
||||
// if (groupId && !firstMount.current && members.length > 0) {
|
||||
if (groupId && !firstMount.current && isPrivate !== null) {
|
||||
if (filterMode === "Recently active") {
|
||||
if (filterMode === 'Recently active') {
|
||||
getMessages();
|
||||
} else if (filterMode === "Newest") {
|
||||
getAllThreads(groupId, "Newest", true);
|
||||
} else if (filterMode === "Oldest") {
|
||||
getAllThreads(groupId, "Oldest", true);
|
||||
} else if (filterMode === 'Newest') {
|
||||
getAllThreads(groupId, 'Newest', true);
|
||||
} else if (filterMode === 'Oldest') {
|
||||
getAllThreads(groupId, 'Oldest', true);
|
||||
}
|
||||
setTempData()
|
||||
setTempData();
|
||||
firstMount.current = true;
|
||||
}
|
||||
}, [groupId, members, filterMode, hide, isPrivate]);
|
||||
@ -428,19 +455,16 @@ export const GroupMail = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
|
||||
let listOfThreadsToDisplay = recentThreads;
|
||||
if (filterMode === "Newest" || filterMode === "Oldest") {
|
||||
if (filterMode === 'Newest' || filterMode === 'Oldest') {
|
||||
listOfThreadsToDisplay = allThreads;
|
||||
}
|
||||
|
||||
const onSubmitNewThread = useCallback(
|
||||
(val: any) => {
|
||||
if (filterMode === "Recently active") {
|
||||
if (filterMode === 'Recently active') {
|
||||
setRecentThreads((prev) => [val, ...prev]);
|
||||
} else if (filterMode === "Newest") {
|
||||
} else if (filterMode === 'Newest') {
|
||||
setAllThreads((prev) => [val, ...prev]);
|
||||
}
|
||||
},
|
||||
@ -461,72 +485,77 @@ export const GroupMail = ({
|
||||
setIsOpenFilterList(false);
|
||||
};
|
||||
|
||||
const refetchThreadsLists = useCallback(()=> {
|
||||
if (filterMode === "Recently active") {
|
||||
const refetchThreadsLists = useCallback(() => {
|
||||
if (filterMode === 'Recently active') {
|
||||
getMessages();
|
||||
} else if (filterMode === "Newest") {
|
||||
getAllThreads(groupId, "Newest", true);
|
||||
} else if (filterMode === "Oldest") {
|
||||
getAllThreads(groupId, "Oldest", true);
|
||||
} else if (filterMode === 'Newest') {
|
||||
getAllThreads(groupId, 'Newest', true);
|
||||
} else if (filterMode === 'Oldest') {
|
||||
getAllThreads(groupId, 'Oldest', true);
|
||||
}
|
||||
}, [filterMode, isPrivate])
|
||||
}, [filterMode, isPrivate]);
|
||||
|
||||
const updateThreadActivityCurrentThread = ()=> {
|
||||
if(!currentThread) return
|
||||
const thread = currentThread
|
||||
const updateThreadActivityCurrentThread = () => {
|
||||
if (!currentThread) return;
|
||||
const thread = currentThread;
|
||||
updateThreadActivity({
|
||||
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
|
||||
})
|
||||
}
|
||||
threadId: thread?.threadId,
|
||||
qortalName: thread?.threadData?.name,
|
||||
groupId: groupId,
|
||||
thread: thread,
|
||||
});
|
||||
};
|
||||
|
||||
const setThreadFunc = (data)=> {
|
||||
const thread = data
|
||||
const setThreadFunc = (data) => {
|
||||
const thread = data;
|
||||
setCurrentThread(thread);
|
||||
if(thread?.threadId && thread?.threadData?.name){
|
||||
updateThreadActivity({
|
||||
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
|
||||
})
|
||||
}
|
||||
if (thread?.threadId && thread?.threadData?.name) {
|
||||
updateThreadActivity({
|
||||
threadId: thread?.threadId,
|
||||
qortalName: thread?.threadData?.name,
|
||||
groupId: groupId,
|
||||
thread: thread,
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
executeEvent("threadFetchMode", {
|
||||
mode: "last-page"
|
||||
executeEvent('threadFetchMode', {
|
||||
mode: 'last-page',
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
if(defaultThread){
|
||||
setThreadFunc(defaultThread)
|
||||
setDefaultThread(null)
|
||||
useEffect(() => {
|
||||
if (defaultThread) {
|
||||
setThreadFunc(defaultThread);
|
||||
setDefaultThread(null);
|
||||
}
|
||||
}, [defaultThread])
|
||||
}, [defaultThread]);
|
||||
|
||||
const combinedListTempAndReal = useMemo(() => {
|
||||
// Combine the two lists
|
||||
const transformTempPublishedList = tempPublishedList.map((item)=> {
|
||||
const transformTempPublishedList = tempPublishedList.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
threadData: item.tempData,
|
||||
threadOwner: item?.name,
|
||||
threadId: item.identifier
|
||||
}
|
||||
})
|
||||
threadId: item.identifier,
|
||||
};
|
||||
});
|
||||
const combined = [...transformTempPublishedList, ...listOfThreadsToDisplay];
|
||||
|
||||
|
||||
// Remove duplicates based on the "identifier"
|
||||
const uniqueItems = new Map();
|
||||
combined.forEach(item => {
|
||||
uniqueItems.set(item.threadId, item); // This will overwrite duplicates, keeping the last occurrence
|
||||
combined.forEach((item) => {
|
||||
uniqueItems.set(item.threadId, item); // This will overwrite duplicates, keeping the last occurrence
|
||||
});
|
||||
|
||||
|
||||
// Convert the map back to an array and sort by "created" timestamp in descending order
|
||||
const sortedList = Array.from(uniqueItems.values()).sort((a, b) =>
|
||||
filterMode === 'Oldest'
|
||||
? a.threadData?.createdAt - b.threadData?.createdAt
|
||||
: b.threadData?.createdAt - a.threadData?.createdAt
|
||||
);
|
||||
|
||||
filterMode === 'Oldest'
|
||||
? a.threadData?.createdAt - b.threadData?.createdAt
|
||||
: b.threadData?.createdAt - a.threadData?.createdAt
|
||||
);
|
||||
|
||||
return sortedList;
|
||||
}, [tempPublishedList, listOfThreadsToDisplay, filterMode]);
|
||||
|
||||
@ -548,9 +577,9 @@ export const GroupMail = ({
|
||||
return (
|
||||
<GroupContainer
|
||||
sx={{
|
||||
position: "relative",
|
||||
overflow: "auto",
|
||||
width: "100%",
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
@ -558,19 +587,19 @@ export const GroupMail = ({
|
||||
anchorEl={anchorElInstanceFilter.current}
|
||||
onClose={handleCloseThreadFilterList}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<InstanceListParent
|
||||
sx={{
|
||||
minHeight: "unset",
|
||||
width: "auto",
|
||||
padding: "0px",
|
||||
minHeight: 'unset',
|
||||
width: 'auto',
|
||||
padding: '0px',
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader></InstanceListHeader>
|
||||
@ -583,7 +612,7 @@ export const GroupMail = ({
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filterMode === filter ? "rgba(74, 158, 244, 1)" : "unset",
|
||||
filterMode === filter ? 'rgba(74, 158, 244, 1)' : 'unset',
|
||||
}}
|
||||
key={filter}
|
||||
>
|
||||
@ -606,12 +635,11 @@ export const GroupMail = ({
|
||||
</Popover>
|
||||
<ThreadContainerFullWidth>
|
||||
<ThreadContainer>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<NewThread
|
||||
@ -626,7 +654,7 @@ export const GroupMail = ({
|
||||
/>
|
||||
<ComposeContainerBlank
|
||||
sx={{
|
||||
height: "auto",
|
||||
height: 'auto',
|
||||
}}
|
||||
>
|
||||
{selectedGroup && !currentThread && (
|
||||
@ -647,17 +675,22 @@ export const GroupMail = ({
|
||||
</ComposeContainerBlank>
|
||||
</Box>
|
||||
<Spacer height="30px" />
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<AllThreadP>{filterMode}</AllThreadP>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<AllThreadP>{filterMode}</AllThreadP>
|
||||
|
||||
<RefreshIcon onClick={refetchThreadsLists} sx={{
|
||||
color: 'white',
|
||||
cursor: 'pointer'
|
||||
}} />
|
||||
<RefreshIcon
|
||||
onClick={refetchThreadsLists}
|
||||
sx={{
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Spacer height="30px" />
|
||||
|
||||
@ -668,112 +701,119 @@ export const GroupMail = ({
|
||||
];
|
||||
const shouldAppearLighter =
|
||||
hasViewedRecent &&
|
||||
filterMode === "Recently active" &&
|
||||
filterMode === 'Recently active' &&
|
||||
thread?.threadData?.createdAt < hasViewedRecent?.timestamp;
|
||||
return (
|
||||
<SingleThreadParent
|
||||
sx={{
|
||||
flexWrap: 'wrap',
|
||||
gap: '15px',
|
||||
height: 'auto'
|
||||
}}
|
||||
sx={{
|
||||
flexWrap: 'wrap',
|
||||
gap: '15px',
|
||||
height: 'auto',
|
||||
}}
|
||||
onClick={() => {
|
||||
setCurrentThread(thread);
|
||||
if(thread?.threadId && thread?.threadData?.name){
|
||||
if (thread?.threadId && thread?.threadData?.name) {
|
||||
updateThreadActivity({
|
||||
threadId: thread?.threadId, qortalName: thread?.threadData?.name, groupId: groupId, thread: thread
|
||||
})
|
||||
threadId: thread?.threadId,
|
||||
qortalName: thread?.threadData?.name,
|
||||
groupId: groupId,
|
||||
thread: thread,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "50px",
|
||||
width: "50px",
|
||||
height: '50px',
|
||||
width: '50px',
|
||||
}}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${thread?.threadData?.name}/qortal_avatar?async=true`}
|
||||
alt={thread?.threadData?.name}
|
||||
>
|
||||
{thread?.threadData?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
|
||||
|
||||
<ThreadInfoColumn>
|
||||
|
||||
<ThreadInfoColumnNameP>
|
||||
<ThreadInfoColumnbyP>by </ThreadInfoColumnbyP>
|
||||
{thread?.threadData?.name}
|
||||
</ThreadInfoColumnNameP>
|
||||
|
||||
|
||||
<ThreadInfoColumnTime>
|
||||
{formatTimestamp(thread?.threadData?.createdAt)}
|
||||
</ThreadInfoColumnTime>
|
||||
</ThreadInfoColumn>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
width: '100%'
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ThreadSingleTitle
|
||||
sx={{
|
||||
fontWeight: shouldAppearLighter && 300,
|
||||
fontSize: isMobile && '18px'
|
||||
fontSize: isMobile && '18px',
|
||||
}}
|
||||
>
|
||||
{thread?.threadData?.title}
|
||||
</ThreadSingleTitle>
|
||||
<Spacer height="10px" />
|
||||
{filterMode === "Recently active" && (
|
||||
{filterMode === 'Recently active' && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ThreadSingleLastMessageP>
|
||||
<ThreadSingleLastMessageSpanP>
|
||||
last message:{" "}
|
||||
last message:{' '}
|
||||
</ThreadSingleLastMessageSpanP>
|
||||
{formatDate(thread?.created)}
|
||||
</ThreadSingleLastMessageP>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<Box onClick={()=> {
|
||||
setTimeout(() => {
|
||||
executeEvent("threadFetchMode", {
|
||||
mode: "last-page"
|
||||
});
|
||||
}, 300);
|
||||
|
||||
|
||||
}} sx={{
|
||||
position: 'absolute',
|
||||
bottom: '2px',
|
||||
right: '2px',
|
||||
borderRadius: '5px',
|
||||
backgroundColor: '#27282c',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
background: 'rgba(255, 255, 255, 0.60)'
|
||||
}
|
||||
}}>
|
||||
<Typography sx={{
|
||||
color: 'white',
|
||||
fontSize: '12px'
|
||||
}}>Last page</Typography>
|
||||
<ArrowForwardIosIcon sx={{
|
||||
color: 'white',
|
||||
fontSize: '12px'
|
||||
}} />
|
||||
<Box
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
executeEvent('threadFetchMode', {
|
||||
mode: 'last-page',
|
||||
});
|
||||
}, 300);
|
||||
}}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: '2px',
|
||||
right: '2px',
|
||||
borderRadius: '5px',
|
||||
backgroundColor: '#27282c',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
background: 'rgba(255, 255, 255, 0.60)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
Last page
|
||||
</Typography>
|
||||
<ArrowForwardIosIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</SingleThreadParent>
|
||||
);
|
||||
@ -781,12 +821,12 @@ export const GroupMail = ({
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{listOfThreadsToDisplay.length >= 20 &&
|
||||
filterMode !== "Recently active" && (
|
||||
filterMode !== 'Recently active' && (
|
||||
<LazyLoad
|
||||
onLoadMore={() => getAllThreads(groupId, filterMode, false)}
|
||||
></LazyLoad>
|
||||
@ -797,7 +837,7 @@ export const GroupMail = ({
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: "Loading threads... please wait.",
|
||||
message: 'Loading threads... please wait.',
|
||||
}}
|
||||
/>
|
||||
</GroupContainer>
|
||||
|
@ -1,11 +1,17 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Box, Button, CircularProgress, Input, Typography } from "@mui/material";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Input,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
import ModalCloseSVG from "../../../assets/svgs/ModalClose.svg";
|
||||
import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg';
|
||||
|
||||
import ComposeIconSVG from "../../../assets/svgs/ComposeIcon.svg";
|
||||
import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg';
|
||||
|
||||
import {
|
||||
AttachmentContainer,
|
||||
@ -22,20 +28,25 @@ import {
|
||||
NewMessageInputRow,
|
||||
NewMessageSendButton,
|
||||
NewMessageSendP,
|
||||
} from "./Mail-styles";
|
||||
} from './Mail-styles';
|
||||
|
||||
import { ReusableModal } from "./ReusableModal";
|
||||
import { Spacer } from "../../../common/Spacer";
|
||||
import { formatBytes } from "../../../utils/Size";
|
||||
import { CreateThreadIcon } from "../../../assets/svgs/CreateThreadIcon";
|
||||
import { SendNewMessage } from "../../../assets/svgs/SendNewMessage";
|
||||
import { TextEditor } from "./TextEditor";
|
||||
import { MyContext, isMobile, pauseAllQueues, resumeAllQueues } from "../../../App";
|
||||
import { getFee } from "../../../background";
|
||||
import TipTap from "../../Chat/TipTap";
|
||||
import { MessageDisplay } from "../../Chat/MessageDisplay";
|
||||
import { CustomizedSnackbars } from "../../Snackbar/Snackbar";
|
||||
import { saveTempPublish } from "../../Chat/GroupAnnouncements";
|
||||
import { ReusableModal } from './ReusableModal';
|
||||
import { Spacer } from '../../../common/Spacer';
|
||||
import { formatBytes } from '../../../utils/Size';
|
||||
import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon';
|
||||
import { SendNewMessage } from '../../../assets/Icons/SendNewMessage';
|
||||
import { TextEditor } from './TextEditor';
|
||||
import {
|
||||
MyContext,
|
||||
isMobile,
|
||||
pauseAllQueues,
|
||||
resumeAllQueues,
|
||||
} from '../../../App';
|
||||
import { getFee } from '../../../background';
|
||||
import TipTap from '../../Chat/TipTap';
|
||||
import { MessageDisplay } from '../../Chat/MessageDisplay';
|
||||
import { CustomizedSnackbars } from '../../Snackbar/Snackbar';
|
||||
import { saveTempPublish } from '../../Chat/GroupAnnouncements';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
@ -54,21 +65,21 @@ export function objectToBase64(obj: any) {
|
||||
const jsonString = JSON.stringify(obj);
|
||||
|
||||
// Step 2: Create a Blob from the JSON string
|
||||
const blob = new Blob([jsonString], { type: "application/json" });
|
||||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||
|
||||
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
if (typeof reader.result === "string") {
|
||||
if (typeof reader.result === 'string') {
|
||||
// Remove 'data:application/json;base64,' prefix
|
||||
const base64 = reader.result.replace(
|
||||
"data:application/json;base64,",
|
||||
""
|
||||
'data:application/json;base64,',
|
||||
''
|
||||
);
|
||||
resolve(base64);
|
||||
} else {
|
||||
reject(new Error("Failed to read the Blob as a base64-encoded string"));
|
||||
reject(new Error('Failed to read the Blob as a base64-encoded string'));
|
||||
}
|
||||
};
|
||||
reader.onerror = () => {
|
||||
@ -94,10 +105,11 @@ export const publishGroupEncryptedResource = async ({
|
||||
identifier,
|
||||
}) => {
|
||||
return new Promise((res, rej) => {
|
||||
window.sendMessage("publishGroupEncryptedResource", {
|
||||
encryptedData,
|
||||
identifier,
|
||||
})
|
||||
window
|
||||
.sendMessage('publishGroupEncryptedResource', {
|
||||
encryptedData,
|
||||
identifier,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
@ -106,19 +118,19 @@ export const publishGroupEncryptedResource = async ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window.sendMessage("encryptSingle", {
|
||||
data,
|
||||
secretKeyObject,
|
||||
})
|
||||
window
|
||||
.sendMessage('encryptSingle', {
|
||||
data,
|
||||
secretKeyObject,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
@ -127,11 +139,12 @@ export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
export const NewThread = ({
|
||||
groupInfo,
|
||||
@ -145,14 +158,14 @@ export const NewThread = ({
|
||||
postReply,
|
||||
myName,
|
||||
setPostReply,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
}: NewMessageProps) => {
|
||||
const { show } = React.useContext(MyContext);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [value, setValue] = useState("");
|
||||
const [value, setValue] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [threadTitle, setThreadTitle] = useState<string>("");
|
||||
const [threadTitle, setThreadTitle] = useState<string>('');
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const editorRef = useRef(null);
|
||||
@ -168,44 +181,42 @@ export const NewThread = ({
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
setValue("");
|
||||
if(setPostReply){
|
||||
setPostReply(null)
|
||||
setValue('');
|
||||
if (setPostReply) {
|
||||
setPostReply(null);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
async function publishQDNResource() {
|
||||
try {
|
||||
pauseAllQueues()
|
||||
if(isSending) return
|
||||
setIsSending(true)
|
||||
let name: string = "";
|
||||
let errorMsg = "";
|
||||
pauseAllQueues();
|
||||
if (isSending) return;
|
||||
setIsSending(true);
|
||||
let name: string = '';
|
||||
let errorMsg = '';
|
||||
|
||||
name = userInfo?.name || "";
|
||||
name = userInfo?.name || '';
|
||||
|
||||
const missingFields: string[] = [];
|
||||
|
||||
if (!isMessage && !threadTitle) {
|
||||
errorMsg = "Please provide a thread title";
|
||||
errorMsg = 'Please provide a thread title';
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
errorMsg = "Cannot send a message without a access to your name";
|
||||
errorMsg = 'Cannot send a message without a access to your name';
|
||||
}
|
||||
if (!groupInfo) {
|
||||
errorMsg = "Cannot access group information";
|
||||
errorMsg = 'Cannot access group information';
|
||||
}
|
||||
|
||||
// if (!description) missingFields.push('subject')
|
||||
if (missingFields.length > 0) {
|
||||
const missingFieldsString = missingFields.join(", ");
|
||||
const missingFieldsString = missingFields.join(', ');
|
||||
const errMsg = `Missing: ${missingFieldsString}`;
|
||||
errorMsg = errMsg;
|
||||
}
|
||||
|
||||
|
||||
if (errorMsg) {
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
@ -217,17 +228,17 @@ export const NewThread = ({
|
||||
}
|
||||
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === "<p></p>")
|
||||
throw new Error("Please provide a first message to the thread");
|
||||
const fee = await getFee("ARBITRARY");
|
||||
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>')
|
||||
throw new Error('Please provide a first message to the thread');
|
||||
const fee = await getFee('ARBITRARY');
|
||||
let feeToShow = fee.fee;
|
||||
if (!isMessage) {
|
||||
feeToShow = +feeToShow * 2;
|
||||
}
|
||||
await show({
|
||||
message: "Would you like to perform a ARBITRARY transaction?",
|
||||
publishFee: feeToShow + " QORT",
|
||||
message: 'Would you like to perform a ARBITRARY transaction?',
|
||||
publishFee: feeToShow + ' QORT',
|
||||
});
|
||||
|
||||
let reply = null;
|
||||
@ -245,20 +256,21 @@ export const NewThread = ({
|
||||
threadOwner: currentThread?.threadData?.name || name,
|
||||
reply,
|
||||
};
|
||||
|
||||
const secretKey = isPrivate === false ? null : await getSecretKey(false, true);
|
||||
|
||||
const secretKey =
|
||||
isPrivate === false ? null : await getSecretKey(false, true);
|
||||
if (!secretKey && isPrivate) {
|
||||
throw new Error("Cannot get group secret key");
|
||||
throw new Error('Cannot get group secret key');
|
||||
}
|
||||
|
||||
|
||||
if (!isMessage) {
|
||||
const idThread = uid.rnd();
|
||||
const idMsg = uid.rnd();
|
||||
const messageToBase64 = await objectToBase64(mailObject);
|
||||
const encryptSingleFirstPost = isPrivate === false ? messageToBase64 : await encryptSingleFunc(
|
||||
messageToBase64,
|
||||
secretKey
|
||||
);
|
||||
const encryptSingleFirstPost =
|
||||
isPrivate === false
|
||||
? messageToBase64
|
||||
: await encryptSingleFunc(messageToBase64, secretKey);
|
||||
const threadObject = {
|
||||
title: threadTitle,
|
||||
groupId: groupInfo.id,
|
||||
@ -267,10 +279,10 @@ export const NewThread = ({
|
||||
};
|
||||
const threadToBase64 = await objectToBase64(threadObject);
|
||||
|
||||
const encryptSingleThread = isPrivate === false ? threadToBase64 : await encryptSingleFunc(
|
||||
threadToBase64,
|
||||
secretKey
|
||||
);
|
||||
const encryptSingleThread =
|
||||
isPrivate === false
|
||||
? threadToBase64
|
||||
: await encryptSingleFunc(threadToBase64, secretKey);
|
||||
let identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`;
|
||||
await publishGroupEncryptedResource({
|
||||
identifier: identifierThread,
|
||||
@ -288,23 +300,27 @@ export const NewThread = ({
|
||||
service: 'DOCUMENT',
|
||||
tempData: threadObject,
|
||||
created: Date.now(),
|
||||
groupId: groupInfo.groupId
|
||||
}
|
||||
groupId: groupInfo.groupId,
|
||||
};
|
||||
const dataToSaveToStoragePost = {
|
||||
name: myName,
|
||||
identifier: identifierPost,
|
||||
service: 'DOCUMENT',
|
||||
tempData: mailObject,
|
||||
created: Date.now(),
|
||||
threadId: identifierThread
|
||||
}
|
||||
await saveTempPublish({data: dataToSaveToStorage, key: 'thread'})
|
||||
await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'})
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully created thread. It may take some time for the publish to propagate",
|
||||
threadId: identifierThread,
|
||||
};
|
||||
await saveTempPublish({ data: dataToSaveToStorage, key: 'thread' });
|
||||
await saveTempPublish({
|
||||
data: dataToSaveToStoragePost,
|
||||
key: 'thread-post',
|
||||
});
|
||||
setOpenSnack(true)
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created thread. It may take some time for the publish to propagate',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
@ -313,35 +329,36 @@ export const NewThread = ({
|
||||
// })
|
||||
// );
|
||||
if (publishCallback) {
|
||||
publishCallback()
|
||||
|
||||
publishCallback();
|
||||
}
|
||||
closeModal();
|
||||
} else {
|
||||
|
||||
if (!currentThread) throw new Error("unable to locate thread Id");
|
||||
if (!currentThread) throw new Error('unable to locate thread Id');
|
||||
const idThread = currentThread.threadId;
|
||||
const messageToBase64 = await objectToBase64(mailObject);
|
||||
const encryptSinglePost = isPrivate === false ? messageToBase64 : await encryptSingleFunc(
|
||||
messageToBase64,
|
||||
secretKey
|
||||
);
|
||||
const encryptSinglePost =
|
||||
isPrivate === false
|
||||
? messageToBase64
|
||||
: await encryptSingleFunc(messageToBase64, secretKey);
|
||||
const idMsg = uid.rnd();
|
||||
let identifier = `thmsg-${idThread}-${idMsg}`;
|
||||
const res = await publishGroupEncryptedResource({
|
||||
identifier: identifier,
|
||||
encryptedData: encryptSinglePost,
|
||||
});
|
||||
|
||||
|
||||
const dataToSaveToStoragePost = {
|
||||
threadId: idThread,
|
||||
name: myName,
|
||||
identifier: identifier,
|
||||
service: 'DOCUMENT',
|
||||
tempData: mailObject,
|
||||
created: Date.now()
|
||||
}
|
||||
await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'})
|
||||
created: Date.now(),
|
||||
};
|
||||
await saveTempPublish({
|
||||
data: dataToSaveToStoragePost,
|
||||
key: 'thread-post',
|
||||
});
|
||||
// await qortalRequest(multiplePublishMsg);
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
@ -350,12 +367,13 @@ export const NewThread = ({
|
||||
// })
|
||||
// );
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully created post. It may take some time for the publish to propagate",
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created post. It may take some time for the publish to propagate',
|
||||
});
|
||||
setOpenSnack(true)
|
||||
if(publishCallback){
|
||||
publishCallback()
|
||||
setOpenSnack(true);
|
||||
if (publishCallback) {
|
||||
publishCallback();
|
||||
}
|
||||
// messageCallback({
|
||||
// identifier,
|
||||
@ -369,17 +387,16 @@ export const NewThread = ({
|
||||
|
||||
closeModal();
|
||||
} catch (error: any) {
|
||||
if(error?.message){
|
||||
if (error?.message) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message: error?.message,
|
||||
});
|
||||
setOpenSnack(true)
|
||||
setOpenSnack(true);
|
||||
}
|
||||
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
resumeAllQueues()
|
||||
resumeAllQueues();
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,56 +406,59 @@ export const NewThread = ({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<ComposeContainer
|
||||
sx={{
|
||||
padding: isMobile ? '5px' : "15px",
|
||||
justifyContent: isMobile ? 'flex-start' : 'revert'
|
||||
padding: isMobile ? '5px' : '15px',
|
||||
justifyContent: isMobile ? 'flex-start' : 'revert',
|
||||
}}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
|
||||
<ComposeP>{currentThread ? 'New Post' : 'New Thread'}</ComposeP>
|
||||
</ComposeContainer>
|
||||
|
||||
<ReusableModal
|
||||
open={isOpen}
|
||||
customStyles={{
|
||||
maxHeight: isMobile ? '95svh' : "95vh",
|
||||
maxWidth: "950px",
|
||||
height: "700px",
|
||||
borderRadius: "12px 12px 0px 0px",
|
||||
background: "#434448",
|
||||
padding: "0px",
|
||||
gap: "0px",
|
||||
maxHeight: isMobile ? '95svh' : '95vh',
|
||||
maxWidth: '950px',
|
||||
height: '700px',
|
||||
borderRadius: '12px 12px 0px 0px',
|
||||
background: '#434448',
|
||||
padding: '0px',
|
||||
gap: '0px',
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader
|
||||
sx={{
|
||||
height: isMobile ? 'auto' : "50px",
|
||||
padding: isMobile ? '5px' : "20px 42px",
|
||||
flexDirection: "row",
|
||||
height: isMobile ? 'auto' : '50px',
|
||||
padding: isMobile ? '5px' : '20px 42px',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: "space-between",
|
||||
backgroundColor: "#434448",
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#434448',
|
||||
}}
|
||||
>
|
||||
<NewMessageHeaderP>
|
||||
{isMessage ? "Post Message" : "New Thread"}
|
||||
{isMessage ? 'Post Message' : 'New Thread'}
|
||||
</NewMessageHeaderP>
|
||||
<CloseContainer sx={{
|
||||
height: '40px'
|
||||
}} onClick={closeModal}>
|
||||
<CloseContainer
|
||||
sx={{
|
||||
height: '40px',
|
||||
}}
|
||||
onClick={closeModal}
|
||||
>
|
||||
<NewMessageCloseImg src={ModalCloseSVG} />
|
||||
</CloseContainer>
|
||||
</InstanceListHeader>
|
||||
<InstanceListContainer
|
||||
sx={{
|
||||
backgroundColor: "#434448",
|
||||
padding: isMobile ? '5px' : "20px 42px",
|
||||
height: "calc(100% - 165px)",
|
||||
backgroundColor: '#434448',
|
||||
padding: isMobile ? '5px' : '20px 42px',
|
||||
height: 'calc(100% - 165px)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
@ -457,19 +477,19 @@ export const NewThread = ({
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
sx={{
|
||||
width: "100%",
|
||||
color: "white",
|
||||
"& .MuiInput-input::placeholder": {
|
||||
color: "rgba(255,255,255, 0.70) !important",
|
||||
fontSize: isMobile ? '14px' : "20px",
|
||||
fontStyle: "normal",
|
||||
width: '100%',
|
||||
color: 'white',
|
||||
'& .MuiInput-input::placeholder': {
|
||||
color: 'rgba(255,255,255, 0.70) !important',
|
||||
fontSize: isMobile ? '14px' : '20px',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
lineHeight: "120%", // 24px
|
||||
letterSpacing: "0.15px",
|
||||
lineHeight: '120%', // 24px
|
||||
letterSpacing: '0.15px',
|
||||
opacity: 1,
|
||||
},
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
// Add any additional styles for the input here
|
||||
}}
|
||||
@ -481,21 +501,18 @@ export const NewThread = ({
|
||||
{postReply && postReply.textContentV2 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxHeight: "120px",
|
||||
overflow: "auto",
|
||||
width: '100%',
|
||||
maxHeight: '120px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<MessageDisplay htmlContent={postReply?.textContentV2} />
|
||||
</Box>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<Spacer height="30px" />
|
||||
|
||||
)}
|
||||
{!isMobile && <Spacer height="30px" />}
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "40vh",
|
||||
maxHeight: '40vh',
|
||||
}}
|
||||
>
|
||||
<TipTap
|
||||
@ -515,41 +532,44 @@ export const NewThread = ({
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter
|
||||
sx={{
|
||||
backgroundColor: "#434448",
|
||||
padding: isMobile ? '5px' : "20px 42px",
|
||||
alignItems: "center",
|
||||
height: isMobile ? 'auto' : "90px",
|
||||
backgroundColor: '#434448',
|
||||
padding: isMobile ? '5px' : '20px 42px',
|
||||
alignItems: 'center',
|
||||
height: isMobile ? 'auto' : '90px',
|
||||
}}
|
||||
>
|
||||
<NewMessageSendButton onClick={sendMail}>
|
||||
{isSending && (
|
||||
<Box sx={{height: '100%', position: 'absolute', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
|
||||
<CircularProgress sx={{
|
||||
|
||||
}} size={'12px'} />
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CircularProgress sx={{}} size={'12px'} />
|
||||
</Box>
|
||||
)}
|
||||
<NewMessageSendP>
|
||||
{isMessage ? "Post" : "Create Thread"}
|
||||
{isMessage ? 'Post' : 'Create Thread'}
|
||||
</NewMessageSendP>
|
||||
{isMessage ? (
|
||||
<SendNewMessage
|
||||
opacity={1}
|
||||
height="25px"
|
||||
width="25px"
|
||||
/>
|
||||
<SendNewMessage opacity={1} height="25px" width="25px" />
|
||||
) : (
|
||||
<CreateThreadIcon
|
||||
opacity={1}
|
||||
height="25px"
|
||||
width="25px"
|
||||
/>
|
||||
<CreateThreadIcon opacity={1} height="25px" width="25px" />
|
||||
)}
|
||||
</NewMessageSendButton>
|
||||
</InstanceFooter>
|
||||
|
||||
</ReusableModal>
|
||||
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -195,7 +195,9 @@ export const Thread = ({
|
||||
[message.identifier]: fullObject,
|
||||
};
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const setTempData = async () => {
|
||||
@ -216,7 +218,9 @@ export const Thread = ({
|
||||
});
|
||||
setTempPublishedList(tempData);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getMailMessages = React.useCallback(
|
||||
@ -461,7 +465,9 @@ export const Thread = ({
|
||||
} else {
|
||||
fullArrayMsg.unshift(fullObject);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
setMessages(fullArrayMsg);
|
||||
} catch (error) {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
useCallback,
|
||||
@ -24,7 +25,6 @@ import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import { AddGroup } from './AddGroup';
|
||||
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||
import CreateIcon from '@mui/icons-material/Create';
|
||||
|
||||
import {
|
||||
AuthenticatedContainerInnerRight,
|
||||
CustomButton,
|
||||
@ -46,7 +46,6 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import { GroupAnnouncements } from '../Chat/GroupAnnouncements';
|
||||
|
||||
import { GroupForum } from '../Chat/GroupForum';
|
||||
import {
|
||||
executeEvent,
|
||||
@ -56,9 +55,7 @@ import {
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { WebSocketActive } from './WebsocketActive';
|
||||
import { useMessageQueue } from '../../MessageQueueContext';
|
||||
import { isExtMsg, isUpdateMsg } from '../../background';
|
||||
import { ContextMenu } from '../ContextMenu';
|
||||
|
||||
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
|
||||
import { ExitIcon } from '../../assets/Icons/ExitIcon';
|
||||
import { HomeDesktop } from './HomeDesktop';
|
||||
@ -113,6 +110,7 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
|
||||
return sortedData[0];
|
||||
};
|
||||
|
||||
interface GroupProps {
|
||||
myAddress: string;
|
||||
isFocused: boolean;
|
||||
@ -134,7 +132,7 @@ export const getGroupAdminsAddress = async (groupNumber: number) => {
|
||||
`${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true`
|
||||
);
|
||||
const groupData = await response.json();
|
||||
let members: any = [];
|
||||
const members: any = [];
|
||||
if (groupData && Array.isArray(groupData?.members)) {
|
||||
for (const member of groupData.members) {
|
||||
if (member.member) {
|
||||
@ -153,7 +151,7 @@ export function validateSecretKey(obj) {
|
||||
}
|
||||
|
||||
// Iterate over each key in the object
|
||||
for (let key in obj) {
|
||||
for (const key in obj) {
|
||||
// Ensure the key is a string representation of a positive integer
|
||||
if (!/^\d+$/.test(key)) {
|
||||
return false;
|
||||
@ -224,7 +222,9 @@ export const decryptResource = async (data: string, fromQortalRequest) => {
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const addDataPublishesFunc = async (data: string, groupId, type) => {
|
||||
@ -247,7 +247,9 @@ export const addDataPublishesFunc = async (data: string, groupId, type) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getDataPublishesFunc = async (groupId, type) => {
|
||||
@ -269,7 +271,9 @@ export const getDataPublishesFunc = async (groupId, type) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export async function getNameInfo(address: string) {
|
||||
@ -337,6 +341,7 @@ export const getNames = async (listOfMembers) => {
|
||||
|
||||
return members;
|
||||
};
|
||||
|
||||
export const getNamesForAdmins = async (admins) => {
|
||||
let members: any = [];
|
||||
|
||||
@ -473,22 +478,20 @@ export const Group = ({
|
||||
}
|
||||
setIsOpenSideViewGroups((prev) => !prev);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
timestampEnterDataRef.current = timestampEnterData;
|
||||
}, [timestampEnterData]);
|
||||
|
||||
useEffect(() => {
|
||||
isFocusedRef.current = isFocused;
|
||||
}, [isFocused]);
|
||||
useEffect(() => {
|
||||
groupSectionRef.current = groupSection;
|
||||
}, [groupSection]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedGroupRef.current = selectedGroup;
|
||||
setSelectedGroupId(selectedGroup?.groupId);
|
||||
}, [selectedGroup]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedDirectRef.current = selectedDirect;
|
||||
}, [selectedDirect]);
|
||||
@ -538,7 +541,9 @@ export const Group = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshHomeDataFunc = () => {
|
||||
@ -565,7 +570,9 @@ export const Group = ({
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -586,7 +593,9 @@ export const Group = ({
|
||||
data.name = name;
|
||||
}
|
||||
setGroupOwner(data);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const directChatHasUnread = useMemo(() => {
|
||||
@ -755,7 +764,7 @@ export const Group = ({
|
||||
setAdmins(addresses);
|
||||
setAdminsWithNames(both);
|
||||
} catch (error) {
|
||||
//error
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -783,7 +792,9 @@ export const Group = ({
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data && data[0]) return data[0].timestamp;
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getLatestRegularChat = async (groups) => {
|
||||
@ -811,7 +822,9 @@ export const Group = ({
|
||||
|
||||
await Promise.all(getGroupData);
|
||||
setGroupChatTimestamps(groupData);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getGroupsProperties = useCallback(async (address) => {
|
||||
@ -826,7 +839,7 @@ export const Group = ({
|
||||
}, {});
|
||||
setGroupsProperties(transformToObject);
|
||||
} catch (error) {
|
||||
// error
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -838,6 +851,7 @@ export const Group = ({
|
||||
Object.keys(groupsProperties)
|
||||
)
|
||||
) {
|
||||
// TODO: empty block. Check it!
|
||||
} else {
|
||||
getGroupsProperties(myAddress);
|
||||
}
|
||||
@ -958,8 +972,11 @@ export const Group = ({
|
||||
const res = await getGroupMembers(groupId);
|
||||
if (groupId !== selectedGroupRef.current?.groupId) return;
|
||||
setMembers(res);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!initiatedGetMembers.current &&
|
||||
@ -1508,6 +1525,7 @@ export const Group = ({
|
||||
setMobileViewMode('home');
|
||||
}
|
||||
if (!isMobile) {
|
||||
// TODO: empty block. Check it!
|
||||
}
|
||||
setDesktopViewMode('home');
|
||||
|
||||
@ -1585,27 +1603,29 @@ export const Group = ({
|
||||
}
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const renderDirects = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: isMobile ? '100%' : '380px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '0px 15px 15px 0px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? `calc(${rootHeight} - 45px)` : '100%',
|
||||
background: !isMobile && 'var(--bg-primary)',
|
||||
borderRadius: !isMobile && '0px 15px 15px 0px',
|
||||
width: isMobile ? '100%' : '380px',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -1618,8 +1638,8 @@ export const Group = ({
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'groups'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Groups"
|
||||
selected={desktopSideView === 'groups'}
|
||||
@ -1631,8 +1651,8 @@ export const Group = ({
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'groups'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
@ -1648,8 +1668,8 @@ export const Group = ({
|
||||
directChatHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'directs'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Messaging"
|
||||
selected={desktopSideView === 'directs'}
|
||||
@ -1660,8 +1680,8 @@ export const Group = ({
|
||||
directChatHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'directs'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
@ -1671,12 +1691,12 @@ export const Group = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{directs.map((direct: any) => (
|
||||
@ -1715,11 +1735,14 @@ export const Group = ({
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
cursor: 'pointer',
|
||||
border: '1px #232428 solid',
|
||||
borderColor: theme.palette.primary,
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
padding: '2px',
|
||||
borderRadius: '2px',
|
||||
background:
|
||||
direct?.address === selectedDirect?.address && 'white',
|
||||
direct?.address === selectedDirect?.address &&
|
||||
theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -1732,8 +1755,8 @@ export const Group = ({
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
background: '#232428',
|
||||
color: 'white',
|
||||
background: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
alt={direct?.name || direct?.address}
|
||||
>
|
||||
@ -1751,7 +1774,7 @@ export const Group = ({
|
||||
style: {
|
||||
color:
|
||||
direct?.address === selectedDirect?.address &&
|
||||
'black',
|
||||
theme.palette.text.primary,
|
||||
textWrap: 'wrap',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
@ -1760,7 +1783,7 @@ export const Group = ({
|
||||
style: {
|
||||
color:
|
||||
direct?.address === selectedDirect?.address &&
|
||||
'black',
|
||||
theme.palette.text.primary,
|
||||
fontSize: '12px',
|
||||
},
|
||||
}}
|
||||
@ -1805,7 +1828,7 @@ export const Group = ({
|
||||
>
|
||||
<CreateIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
New Chat
|
||||
@ -1824,7 +1847,7 @@ export const Group = ({
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: isMobile ? `calc(${rootHeight} - 45px)` : '100%',
|
||||
background: !isMobile && 'var(--bg-primary)',
|
||||
background: !isMobile && theme.palette.background.default,
|
||||
borderRadius: !isMobile && '0px 15px 15px 0px',
|
||||
}}
|
||||
>
|
||||
@ -1848,8 +1871,8 @@ export const Group = ({
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'groups'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Groups"
|
||||
selected={desktopSideView === 'groups'}
|
||||
@ -1861,8 +1884,8 @@ export const Group = ({
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'groups'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
@ -1878,8 +1901,8 @@ export const Group = ({
|
||||
directChatHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'directs'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Messaging"
|
||||
selected={desktopSideView === 'directs'}
|
||||
@ -1890,8 +1913,8 @@ export const Group = ({
|
||||
directChatHasUnread
|
||||
? 'var(--unread)'
|
||||
: desktopSideView === 'directs'
|
||||
? 'white'
|
||||
: 'rgba(250, 250, 250, 0.5)'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
@ -1901,15 +1924,15 @@ export const Group = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
visibility: chatMode === 'directs' && 'hidden',
|
||||
position: chatMode === 'directs' && 'fixed',
|
||||
left: chatMode === 'directs' && '-1000px',
|
||||
overflowY: 'auto',
|
||||
position: chatMode === 'directs' && 'fixed',
|
||||
visibility: chatMode === 'directs' && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{groups.map((group: any) => (
|
||||
@ -1951,14 +1974,17 @@ export const Group = ({
|
||||
}}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
cursor: 'pointer',
|
||||
border: '1px #232428 solid',
|
||||
padding: '2px',
|
||||
borderRadius: '2px',
|
||||
background:
|
||||
group?.groupId === selectedGroup?.groupId && 'white',
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.background.default,
|
||||
borderColor: theme.palette.primary,
|
||||
borderRadius: '2px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
cursor: 'pointer',
|
||||
flexDirection: 'column',
|
||||
padding: '2px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
@ -1968,22 +1994,22 @@ export const Group = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
{groupsProperties[group?.groupId]?.isOpen === false ? (
|
||||
<Box
|
||||
sx={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: '#232428',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
justifyContent: 'center',
|
||||
width: '40px',
|
||||
}}
|
||||
>
|
||||
<LockIcon
|
||||
@ -1995,13 +2021,13 @@ export const Group = ({
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: '#232428',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
justifyContent: 'center',
|
||||
width: '40px',
|
||||
}}
|
||||
>
|
||||
<NoEncryptionGmailerrorredIcon
|
||||
@ -2010,15 +2036,6 @@ export const Group = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
// <Avatar
|
||||
// sx={{
|
||||
// background: "#232428",
|
||||
// color: "white",
|
||||
// }}
|
||||
// alt={group?.groupName}
|
||||
// >
|
||||
// {group.groupName?.charAt(0)}
|
||||
// </Avatar>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
@ -2034,14 +2051,14 @@ export const Group = ({
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
'black',
|
||||
theme.palette.text.primary,
|
||||
},
|
||||
}} // Change the color of the primary text
|
||||
secondaryTypographyProps={{
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
'black',
|
||||
theme.palette.text.primary,
|
||||
fontSize: '12px',
|
||||
},
|
||||
}}
|
||||
@ -2084,10 +2101,10 @@ export const Group = ({
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
padding: '10px',
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{chatMode === 'groups' && (
|
||||
@ -2099,11 +2116,12 @@ export const Group = ({
|
||||
>
|
||||
<AddCircleOutlineIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
Group Mgmt
|
||||
Group
|
||||
</CustomButton>
|
||||
|
||||
{!isRunningPublicNode && (
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
@ -2116,7 +2134,7 @@ export const Group = ({
|
||||
>
|
||||
<PersonOffIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</CustomButton>
|
||||
@ -2133,7 +2151,7 @@ export const Group = ({
|
||||
>
|
||||
<CreateIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
New Chat
|
||||
@ -2150,6 +2168,7 @@ export const Group = ({
|
||||
myAddress={myAddress}
|
||||
setIsLoadingGroups={setIsLoadingGroups}
|
||||
/>
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
@ -2159,11 +2178,11 @@ export const Group = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: isMobile ? '100%' : '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: isMobile ? '100%' : '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isMobile &&
|
||||
@ -2191,6 +2210,7 @@ export const Group = ({
|
||||
desktopViewMode === 'chat' &&
|
||||
desktopSideView !== 'directs' &&
|
||||
renderGroups()}
|
||||
|
||||
{!isMobile &&
|
||||
desktopViewMode === 'chat' &&
|
||||
desktopSideView === 'directs' &&
|
||||
@ -2214,26 +2234,26 @@ export const Group = ({
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
marginTop: '14px',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
height: '15px',
|
||||
justifyContent: 'center',
|
||||
marginTop: '14px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '320px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
@ -2248,10 +2268,10 @@ export const Group = ({
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '50px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '50px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -2268,17 +2288,15 @@ export const Group = ({
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
|
||||
right: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
background: theme.palette.background.default,
|
||||
bottom: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
top: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
background: '#27282c',
|
||||
zIndex: 5,
|
||||
height: isMobile && `calc(${rootHeight} - 45px)`,
|
||||
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
|
||||
|
||||
left: !(desktopViewMode === 'chat') ? '-100000px' : '0px',
|
||||
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
|
||||
position: 'absolute',
|
||||
right: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
top: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
zIndex: 5,
|
||||
}}
|
||||
>
|
||||
<ChatDirect
|
||||
@ -2292,7 +2310,6 @@ export const Group = ({
|
||||
balance={balance}
|
||||
close={() => {
|
||||
setSelectedDirect(null);
|
||||
|
||||
setNewChat(false);
|
||||
}}
|
||||
setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
|
||||
@ -2303,18 +2320,18 @@ export const Group = ({
|
||||
{desktopViewMode === 'chat' && !selectedGroup && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 400,
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
No group selected
|
||||
@ -2407,12 +2424,12 @@ export const Group = ({
|
||||
!secretKeyPublishDate && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
padding: '20px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
@ -2534,9 +2551,9 @@ export const Group = ({
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
bottom: '25px',
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
bottom: '25px',
|
||||
right: '25px',
|
||||
zIndex: 100,
|
||||
}}
|
||||
@ -2586,24 +2603,23 @@ export const Group = ({
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
background: theme.palette.background.default,
|
||||
bottom: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
height: isMobile && `calc(${rootHeight} - 45px)`,
|
||||
left: !(desktopViewMode === 'chat') ? '-100000px' : '0px',
|
||||
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
|
||||
position: 'absolute',
|
||||
right: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
bottom: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
top: !(desktopViewMode === 'chat') ? 'unset' : '0px',
|
||||
background: '#27282c',
|
||||
zIndex: 5,
|
||||
height: isMobile && `calc(${rootHeight} - 45px)`,
|
||||
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
|
||||
|
||||
left: !(desktopViewMode === 'chat') ? '-100000px' : '0px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<ChatDirect
|
||||
|
@ -1,22 +1,17 @@
|
||||
import * as React from "react";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import CommentIcon from "@mui/icons-material/Comment";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import GroupAddIcon from "@mui/icons-material/GroupAdd";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { getGroupNames } from "./UserListOfInvites";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { getBaseApiReact, isMobile } from "../../App";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import * as React from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import GroupAddIcon from '@mui/icons-material/GroupAdd';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { Box, ButtonBase, Collapse, Typography, useTheme } from '@mui/material';
|
||||
import { getGroupNames } from './UserListOfInvites';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { getBaseApiReact, isMobile } from '../../App';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
|
||||
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
|
||||
@ -37,11 +32,14 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
|
||||
setGroupsWithJoinRequests(resMoreData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (myAddress) {
|
||||
getJoinRequests();
|
||||
@ -51,57 +49,65 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "322px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: "0px 20px",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start'
|
||||
justifyContent: 'flex-start',
|
||||
padding: '0px 20px',
|
||||
width: '322px',
|
||||
}}
|
||||
onClick={()=> setIsExpanded((prev)=> !prev)}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
>
|
||||
Group Invites {groupsWithJoinRequests?.length > 0 && ` (${groupsWithJoinRequests?.length})`}
|
||||
Group Invites{' '}
|
||||
{groupsWithJoinRequests?.length > 0 &&
|
||||
` (${groupsWithJoinRequests?.length})`}
|
||||
</Typography>
|
||||
{isExpanded ? <ExpandLessIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}} /> : (
|
||||
<ExpandMoreIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}}/>
|
||||
)}
|
||||
{isExpanded ? (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ExpandMoreIcon
|
||||
sx={{
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
width: "322px",
|
||||
height: isMobile ? "165px" : "250px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
padding: "20px",
|
||||
borderRadius: "19px",
|
||||
bgcolor: theme.palette.background.paper,
|
||||
borderRadius: '19px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '165px' : '250px',
|
||||
padding: '20px',
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
{loading && groupsWithJoinRequests.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
@ -110,18 +116,18 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
{!loading && groupsWithJoinRequests.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "11px",
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '11px',
|
||||
fontWeight: 400,
|
||||
color: "rgba(255, 255, 255, 0.2)",
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
@ -130,11 +136,11 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
)}
|
||||
<List
|
||||
sx={{
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
maxWidth: 360,
|
||||
bgcolor: "background.paper",
|
||||
maxHeight: "300px",
|
||||
overflow: "auto",
|
||||
bgcolor: theme.palette.background.paper,
|
||||
maxHeight: '300px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
className="scrollable-container"
|
||||
>
|
||||
@ -142,13 +148,13 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
return (
|
||||
<ListItem
|
||||
sx={{
|
||||
marginBottom: "20px",
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
key={group?.groupId}
|
||||
onClick={() => {
|
||||
setOpenAddGroup(true);
|
||||
setTimeout(() => {
|
||||
executeEvent("openGroupInvitesRequest", {});
|
||||
executeEvent('openGroupInvitesRequest', {});
|
||||
}, 300);
|
||||
}}
|
||||
disablePadding
|
||||
@ -156,8 +162,8 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
<IconButton edge="end" aria-label="comments">
|
||||
<GroupAddIcon
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '18px',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
@ -166,8 +172,8 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
<ListItemButton disableRipple role={undefined} dense>
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "13px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '13px',
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -6,45 +6,46 @@ import {
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { Label } from "./AddGroup";
|
||||
import { getFee } from "../../background";
|
||||
} from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { Label } from './AddGroup';
|
||||
import { getFee } from '../../background';
|
||||
|
||||
export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
const [value, setValue] = useState("");
|
||||
const [value, setValue] = useState('');
|
||||
const [expiryTime, setExpiryTime] = useState<string>('259200');
|
||||
const [isLoadingInvite, setIsLoadingInvite] = useState(false)
|
||||
const [isLoadingInvite, setIsLoadingInvite] = useState(false);
|
||||
const inviteMember = async () => {
|
||||
try {
|
||||
const fee = await getFee('GROUP_INVITE')
|
||||
const fee = await getFee('GROUP_INVITE');
|
||||
await show({
|
||||
message: "Would you like to perform a GROUP_INVITE transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
setIsLoadingInvite(true)
|
||||
message: 'Would you like to perform a GROUP_INVITE transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoadingInvite(true);
|
||||
if (!expiryTime || !value) return;
|
||||
new Promise((res, rej) => {
|
||||
window.sendMessage("inviteToGroup", {
|
||||
groupId,
|
||||
qortalAddress: value,
|
||||
inviteTime: +expiryTime,
|
||||
})
|
||||
window
|
||||
.sendMessage('inviteToGroup', {
|
||||
groupId,
|
||||
qortalAddress: value,
|
||||
inviteTime: +expiryTime,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`,
|
||||
});
|
||||
setOpenSnack(true);
|
||||
res(response);
|
||||
|
||||
setValue("");
|
||||
|
||||
setValue('');
|
||||
return;
|
||||
}
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message: response?.error,
|
||||
});
|
||||
setOpenSnack(true);
|
||||
@ -52,16 +53,17 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error?.message || "An error occurred",
|
||||
type: 'error',
|
||||
message: error?.message || 'An error occurred',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
|
||||
});
|
||||
} catch (error) {} finally {
|
||||
setIsLoadingInvite(false)
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingInvite(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,8 +74,8 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
Invite member
|
||||
@ -83,8 +85,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
placeholder="Name or address"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
<Spacer height="20px" />
|
||||
|
||||
<Spacer height="20px" />
|
||||
<Label>Invitation Expiry Time</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
@ -105,7 +106,14 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
<MenuItem value={2592000}>30 days</MenuItem>
|
||||
</Select>
|
||||
<Spacer height="20px" />
|
||||
<LoadingButton variant="contained" loadingPosition="start" loading={isLoadingInvite} onClick={inviteMember}>Invite</LoadingButton>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
loadingPosition="start"
|
||||
loading={isLoadingInvite}
|
||||
onClick={inviteMember}
|
||||
>
|
||||
Invite
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
} from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@ -25,44 +25,44 @@ import {
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
} from '@mui/material';
|
||||
|
||||
import { getNameInfo } from "./Group";
|
||||
import { getBaseApi, getFee } from "../../background";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import NoEncryptionGmailerrorredIcon from "@mui/icons-material/NoEncryptionGmailerrorred";
|
||||
import { getNameInfo } from './Group';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
isMobile,
|
||||
} from "../../App";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { RequestQueueWithPromise } from "../../utils/queue/queue";
|
||||
import { useRecoilState } from "recoil";
|
||||
} from '../../App';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import {
|
||||
myGroupsWhereIAmAdminAtom,
|
||||
promotionTimeIntervalAtom,
|
||||
promotionsAtom,
|
||||
} from "../../atoms/global";
|
||||
import { Label } from "./AddGroup";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { getGroupNames } from "./UserListOfInvites";
|
||||
import { WrapperUserAction } from "../WrapperUserAction";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import ErrorBoundary from "../../common/ErrorBoundary";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
} from '../../atoms/global';
|
||||
import { Label } from './AddGroup';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { getGroupNames } from './UserListOfInvites';
|
||||
import { WrapperUserAction } from '../WrapperUserAction';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import ErrorBoundary from '../../common/ErrorBoundary';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
export const requestQueuePromos = new RequestQueueWithPromise(20);
|
||||
|
||||
export function utf8ToBase64(inputString: string): string {
|
||||
// Encode the string as UTF-8
|
||||
const utf8String = encodeURIComponent(inputString).replace(
|
||||
/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(Number("0x" + p1))
|
||||
(match, p1) => String.fromCharCode(Number('0x' + p1))
|
||||
);
|
||||
|
||||
// Convert the UTF-8 encoded string to base64
|
||||
@ -83,7 +83,7 @@ export const ListOfGroupPromotions = () => {
|
||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isShowModal, setIsShowModal] = useState(false);
|
||||
const [text, setText] = useState("");
|
||||
const [text, setText] = useState('');
|
||||
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
|
||||
myGroupsWhereIAmAdminAtom
|
||||
);
|
||||
@ -115,10 +115,12 @@ export const ListOfGroupPromotions = () => {
|
||||
useEffect(() => {
|
||||
try {
|
||||
(async () => {
|
||||
const feeRes = await getFee("ARBITRARY");
|
||||
const feeRes = await getFee('ARBITRARY');
|
||||
setFee(feeRes?.fee);
|
||||
})();
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
const getPromotions = useCallback(async () => {
|
||||
try {
|
||||
@ -126,9 +128,9 @@ export const ListOfGroupPromotions = () => {
|
||||
const identifier = `group-promotions-ui24-`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=100&includemetadata=false&reverse=true&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
@ -142,7 +144,7 @@ export const ListOfGroupPromotions = () => {
|
||||
promo.name
|
||||
}/${promo.identifier}`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
try {
|
||||
@ -164,7 +166,7 @@ export const ListOfGroupPromotions = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching promo:", error);
|
||||
console.error('Error fetching promo:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -222,10 +224,10 @@ export const ListOfGroupPromotions = () => {
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage("publishOnQDN", {
|
||||
.sendMessage('publishOnQDN', {
|
||||
data: data,
|
||||
identifier: identifier,
|
||||
service: "DOCUMENT",
|
||||
service: 'DOCUMENT',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
@ -235,23 +237,23 @@ export const ListOfGroupPromotions = () => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message:
|
||||
"Successfully published promotion. It may take a couple of minutes for the promotion to appear",
|
||||
'Successfully published promotion. It may take a couple of minutes for the promotion to appear',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setText("");
|
||||
setText('');
|
||||
setSelectedGroup(null);
|
||||
setIsShowModal(false);
|
||||
} catch (error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message:
|
||||
error?.message || "Error publishing the promotion. Please try again",
|
||||
error?.message || 'Error publishing the promotion. Please try again',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
} finally {
|
||||
@ -262,30 +264,30 @@ export const ListOfGroupPromotions = () => {
|
||||
const handleJoinGroup = async (group, isOpen) => {
|
||||
try {
|
||||
const groupId = group.groupId;
|
||||
const fee = await getFee("JOIN_GROUP");
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
await show({
|
||||
message: "Would you like to perform an JOIN_GROUP transaction?",
|
||||
publishFee: fee.fee + " QORT",
|
||||
message: 'Would you like to perform an JOIN_GROUP transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoadingJoinGroup(true);
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage("joinGroup", {
|
||||
.sendMessage('joinGroup', {
|
||||
groupId,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
type: 'success',
|
||||
message:
|
||||
"Successfully requested to join group. It may take a couple of minutes for the changes to propagate",
|
||||
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: "joined-group",
|
||||
type: 'joined-group',
|
||||
label: `Joined Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Joined Group ${group?.groupName}: success!`,
|
||||
done: false,
|
||||
@ -297,7 +299,7 @@ export const ListOfGroupPromotions = () => {
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: "joined-group-request",
|
||||
type: 'joined-group-request',
|
||||
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Requested to join Group ${group?.groupName}: success!`,
|
||||
done: false,
|
||||
@ -313,7 +315,7 @@ export const ListOfGroupPromotions = () => {
|
||||
return;
|
||||
} else {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
type: 'error',
|
||||
message: response?.error,
|
||||
});
|
||||
setOpenSnack(true);
|
||||
@ -322,8 +324,8 @@ export const ListOfGroupPromotions = () => {
|
||||
})
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error.message || "An error occurred",
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
@ -339,55 +341,58 @@ export const ListOfGroupPromotions = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
marginTop: "20px",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
marginTop: '20px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `0px ${isExpanded ? "24px" : "20px"}`,
|
||||
gap: "10px",
|
||||
justifyContent: "flex-start",
|
||||
alignSelf: isExpanded && "flex-start",
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: `0px ${isExpanded ? '24px' : '20px'}`,
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start',
|
||||
alignSelf: isExpanded && 'flex-start',
|
||||
}}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
>
|
||||
Group promotions {promotions.length > 0 && ` (${promotions.length})`}
|
||||
Group promotions{' '}
|
||||
{promotions.length > 0 && ` (${promotions.length})`}
|
||||
</Typography>
|
||||
{isExpanded ? (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ExpandMoreIcon
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Box
|
||||
style={{
|
||||
width: "330px",
|
||||
width: '330px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@ -396,24 +401,24 @@ export const ListOfGroupPromotions = () => {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "320px" : "750px",
|
||||
maxWidth: "90%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: "0px 20px",
|
||||
width: isMobile ? '320px' : '750px',
|
||||
maxWidth: '90%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0px 20px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
></Typography>
|
||||
@ -421,7 +426,7 @@ export const ListOfGroupPromotions = () => {
|
||||
variant="contained"
|
||||
onClick={() => setIsShowModal(true)}
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
Add Promotion
|
||||
@ -431,22 +436,22 @@ export const ListOfGroupPromotions = () => {
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "320px" : "750px",
|
||||
maxWidth: "90%",
|
||||
maxHeight: "700px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
padding: "20px 0px",
|
||||
borderRadius: "19px",
|
||||
width: isMobile ? '320px' : '750px',
|
||||
maxWidth: '90%',
|
||||
maxHeight: '700px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'background.paper',
|
||||
padding: '20px 0px',
|
||||
borderRadius: '19px',
|
||||
}}
|
||||
>
|
||||
{loading && promotions.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
@ -455,18 +460,18 @@ export const ListOfGroupPromotions = () => {
|
||||
{!loading && promotions.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "11px",
|
||||
fontSize: '11px',
|
||||
fontWeight: 400,
|
||||
color: "rgba(255, 255, 255, 0.2)",
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
@ -475,11 +480,11 @@ export const ListOfGroupPromotions = () => {
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
height: "600px",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: '600px',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@ -487,24 +492,24 @@ export const ListOfGroupPromotions = () => {
|
||||
className="scrollable-container"
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
height: "0px",
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
height: '0px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: rowVirtualizer.getTotalSize(),
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
@ -516,17 +521,17 @@ export const ListOfGroupPromotions = () => {
|
||||
ref={rowVirtualizer.measureElement} //measure dynamic row height
|
||||
key={promotion?.identifier}
|
||||
style={{
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: "50%", // Move to the center horizontally
|
||||
left: '50%', // Move to the center horizontally
|
||||
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
|
||||
width: "100%", // Control width (90% of the parent)
|
||||
padding: "10px 0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
overscrollBehavior: "none",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
width: '100%', // Control width (90% of the parent)
|
||||
padding: '10px 0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
overscrollBehavior: 'none',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
@ -538,47 +543,47 @@ export const ListOfGroupPromotions = () => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "0px 20px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
padding: '0px 20px',
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
open={openPopoverIndex === promotion?.groupId}
|
||||
anchorEl={popoverAnchor}
|
||||
onClose={(event, reason) => {
|
||||
if (reason === "backdropClick") {
|
||||
if (reason === 'backdropClick') {
|
||||
// Prevent closing on backdrop click
|
||||
return;
|
||||
}
|
||||
handlePopoverClose(); // Close only on other events like Esc key press
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
style={{ marginTop: "8px" }}
|
||||
style={{ marginTop: '8px' }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "325px",
|
||||
height: "auto",
|
||||
maxHeight: "400px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
padding: "10px",
|
||||
width: '325px',
|
||||
height: 'auto',
|
||||
maxHeight: '400px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
@ -586,17 +591,17 @@ export const ListOfGroupPromotions = () => {
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Number of members:{" "}
|
||||
Number of members:{' '}
|
||||
{` ${promotion?.memberCount}`}
|
||||
</Typography>
|
||||
{promotion?.description && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
@ -606,7 +611,7 @@ export const ListOfGroupPromotions = () => {
|
||||
{promotion?.isOpen === false && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "13px",
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
@ -618,11 +623,11 @@ export const ListOfGroupPromotions = () => {
|
||||
<Spacer height="5px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
@ -652,23 +657,23 @@ export const ListOfGroupPromotions = () => {
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '15px',
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: "#27282c",
|
||||
color: "white",
|
||||
backgroundColor: '#27282c',
|
||||
color: 'white',
|
||||
}}
|
||||
alt={promotion?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
@ -680,8 +685,8 @@ export const ListOfGroupPromotions = () => {
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
fontFamily: 'Inter',
|
||||
color: 'cadetBlue',
|
||||
}}
|
||||
>
|
||||
{promotion?.name}
|
||||
@ -690,8 +695,8 @@ export const ListOfGroupPromotions = () => {
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
fontFamily: 'Inter',
|
||||
color: 'cadetBlue',
|
||||
}}
|
||||
>
|
||||
{promotion?.groupName}
|
||||
@ -700,42 +705,42 @@ export const ListOfGroupPromotions = () => {
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{promotion?.isOpen === false && (
|
||||
<LockIcon
|
||||
sx={{
|
||||
color: "var(--green)",
|
||||
color: 'var(--green)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{promotion?.isOpen === true && (
|
||||
<NoEncryptionGmailerrorredIcon
|
||||
sx={{
|
||||
color: "var(--danger)",
|
||||
color: 'var(--danger)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "15px",
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{promotion?.isOpen
|
||||
? "Public group"
|
||||
: "Private group"}
|
||||
? 'Public group'
|
||||
: 'Private group'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
fontFamily: 'Inter',
|
||||
color: 'cadetBlue',
|
||||
}}
|
||||
>
|
||||
{promotion?.data}
|
||||
@ -743,9 +748,9 @@ export const ListOfGroupPromotions = () => {
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@ -754,8 +759,8 @@ export const ListOfGroupPromotions = () => {
|
||||
handlePopoverOpen(event, promotion?.groupId)
|
||||
}
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
color: "white",
|
||||
fontSize: '12px',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
Join Group: {` ${promotion?.groupName}`}
|
||||
@ -783,7 +788,7 @@ export const ListOfGroupPromotions = () => {
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Promote your group to non-members"}
|
||||
{'Promote your group to non-members'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
@ -791,14 +796,14 @@ export const ListOfGroupPromotions = () => {
|
||||
group.
|
||||
</DialogContentText>
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
Max 200 characters. Publish Fee: {fee && fee} {" QORT"}
|
||||
Max 200 characters. Publish Fee: {fee && fee} {' QORT'}
|
||||
</DialogContentText>
|
||||
<Spacer height="20px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Select a group</Label>
|
||||
@ -832,11 +837,11 @@ export const ListOfGroupPromotions = () => {
|
||||
}}
|
||||
multiline={true}
|
||||
sx={{
|
||||
"& .MuiFormLabel-root": {
|
||||
color: "white",
|
||||
'& .MuiFormLabel-root': {
|
||||
color: 'white',
|
||||
},
|
||||
"& .MuiFormLabel-root.Mui-focused": {
|
||||
color: "white",
|
||||
'& .MuiFormLabel-root.Mui-focused': {
|
||||
color: 'white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@ -1,36 +1,36 @@
|
||||
import * as React from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Slide from "@mui/material/Slide";
|
||||
import { TransitionProps } from "@mui/material/transitions";
|
||||
import ListOfMembers from "./ListOfMembers";
|
||||
import { InviteMember } from "./InviteMember";
|
||||
import { ListOfInvites } from "./ListOfInvites";
|
||||
import { ListOfBans } from "./ListOfBans";
|
||||
import { ListOfJoinRequests } from "./ListOfJoinRequests";
|
||||
import { Box, ButtonBase, Card, Tab, Tabs } from "@mui/material";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
||||
import { getGroupMembers, getNames } from "./Group";
|
||||
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
|
||||
import { getFee } from "../../background";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import * as React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import List from '@mui/material/List';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
import ListOfMembers from './ListOfMembers';
|
||||
import { InviteMember } from './InviteMember';
|
||||
import { ListOfInvites } from './ListOfInvites';
|
||||
import { ListOfBans } from './ListOfBans';
|
||||
import { ListOfJoinRequests } from './ListOfJoinRequests';
|
||||
import { Box, ButtonBase, Card, Tab, Tabs } from '@mui/material';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { MyContext, getBaseApiReact, isMobile } from '../../App';
|
||||
import { getGroupMembers, getNames } from './Group';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import InsertLinkIcon from '@mui/icons-material/InsertLink';
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,16 +50,16 @@ export const ManageMembers = ({
|
||||
selectedGroup,
|
||||
|
||||
isAdmin,
|
||||
isOwner
|
||||
isOwner,
|
||||
}) => {
|
||||
const [membersWithNames, setMembersWithNames] = React.useState([]);
|
||||
const [tab, setTab] = React.useState("create");
|
||||
const [tab, setTab] = React.useState('create');
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoadingMembers, setIsLoadingMembers] = React.useState(false)
|
||||
const [isLoadingLeave, setIsLoadingLeave] = React.useState(false)
|
||||
const [groupInfo, setGroupInfo] = React.useState(null)
|
||||
const [isLoadingMembers, setIsLoadingMembers] = React.useState(false);
|
||||
const [isLoadingLeave, setIsLoadingLeave] = React.useState(false);
|
||||
const [groupInfo, setGroupInfo] = React.useState(null);
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
@ -69,20 +69,20 @@ export const ManageMembers = ({
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
|
||||
const handleLeaveGroup = async () => {
|
||||
try {
|
||||
setIsLoadingLeave(true)
|
||||
const fee = await getFee('LEAVE_GROUP')
|
||||
setIsLoadingLeave(true);
|
||||
const fee = await getFee('LEAVE_GROUP');
|
||||
await show({
|
||||
message: "Would you like to perform an LEAVE_GROUP transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
message: 'Would you like to perform an LEAVE_GROUP transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("leaveGroup", {
|
||||
groupId: selectedGroup?.groupId,
|
||||
})
|
||||
window
|
||||
.sendMessage('leaveGroup', {
|
||||
groupId: selectedGroup?.groupId,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setTxList((prev) => [
|
||||
@ -98,8 +98,9 @@ export const ManageMembers = ({
|
||||
]);
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully requested to leave group. It may take a couple of minutes for the changes to propagate",
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to leave group. It may take a couple of minutes for the changes to propagate',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
return;
|
||||
@ -107,57 +108,62 @@ export const ManageMembers = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
|
||||
});
|
||||
} catch (error) {} finally {
|
||||
setIsLoadingLeave(false)
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingLeave(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getMembersWithNames = React.useCallback(async (groupId) => {
|
||||
const getMembersWithNames = React.useCallback(async (groupId) => {
|
||||
try {
|
||||
setIsLoadingMembers(true)
|
||||
setIsLoadingMembers(true);
|
||||
const res = await getGroupMembers(groupId);
|
||||
const resWithNames = await getNames(res.members);
|
||||
setMembersWithNames(resWithNames);
|
||||
setIsLoadingMembers(false)
|
||||
} catch (error) {}
|
||||
setIsLoadingMembers(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getMembers = async (groupId) => {
|
||||
try {
|
||||
const res = await getGroupMembers(groupId);
|
||||
setMembersWithNames(res?.members || []);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
const getGroupInfo = async (groupId) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/groups/${groupId}`
|
||||
);
|
||||
const groupData = await response.json();
|
||||
setGroupInfo(groupData)
|
||||
} catch (error) {}
|
||||
const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`);
|
||||
const groupData = await response.json();
|
||||
setGroupInfo(groupData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(()=> {
|
||||
if(selectedGroup?.groupId){
|
||||
getMembers(selectedGroup?.groupId)
|
||||
getGroupInfo(selectedGroup?.groupId)
|
||||
React.useEffect(() => {
|
||||
if (selectedGroup?.groupId) {
|
||||
getMembers(selectedGroup?.groupId);
|
||||
getGroupInfo(selectedGroup?.groupId);
|
||||
}
|
||||
}, [selectedGroup?.groupId])
|
||||
}, [selectedGroup?.groupId]);
|
||||
|
||||
const openGroupJoinRequestFunc = ()=> {
|
||||
setValue(4)
|
||||
}
|
||||
const openGroupJoinRequestFunc = () => {
|
||||
setValue(4);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
subscribeToEvent("openGroupJoinRequest", openGroupJoinRequestFunc);
|
||||
subscribeToEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("openGroupJoinRequest", openGroupJoinRequestFunc);
|
||||
unsubscribeFromEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -169,7 +175,7 @@ export const ManageMembers = ({
|
||||
onClose={handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar sx={{ position: "relative", bgcolor: "#232428" }}>
|
||||
<AppBar sx={{ position: 'relative', bgcolor: '#232428' }}>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
|
||||
Manage Members
|
||||
@ -186,117 +192,136 @@ export const ManageMembers = ({
|
||||
</AppBar>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: "#27282c",
|
||||
bgcolor: '#27282c',
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
color: "white",
|
||||
overflowY: 'auto',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons="auto" // Show scroll buttons automatically
|
||||
allowScrollButtonsMobile // Show scroll buttons on mobile as well
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
maxWidth: '100%', // Ensure the tabs container fits within the available space
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="List of members"
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Invite new member"
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="List of invites"
|
||||
{...a11yProps(2)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="List of bans"
|
||||
{...a11yProps(3)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Join requests"
|
||||
{...a11yProps(4)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
color: "white",
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
variant="scrollable" // Make tabs scrollable
|
||||
scrollButtons="auto" // Show scroll buttons automatically
|
||||
allowScrollButtonsMobile // Show scroll buttons on mobile as well
|
||||
sx={{
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
maxWidth: '100%', // Ensure the tabs container fits within the available space
|
||||
overflow: 'hidden', // Prevents overflow on small screens
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="List of members"
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Invite new member"
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="List of invites"
|
||||
{...a11yProps(2)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="List of bans"
|
||||
{...a11yProps(3)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Join requests"
|
||||
{...a11yProps(4)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
color: 'white',
|
||||
},
|
||||
fontSize: isMobile ? '0.75rem' : '1rem',
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
<Card sx={{
|
||||
padding: '10px',
|
||||
cursor: 'default',
|
||||
}}>
|
||||
<Card
|
||||
sx={{
|
||||
padding: '10px',
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography>GroupId: {groupInfo?.groupId}</Typography>
|
||||
<Typography>GroupName: {groupInfo?.groupName}</Typography>
|
||||
<Typography>Number of members: {groupInfo?.memberCount}</Typography>
|
||||
<ButtonBase sx={{
|
||||
gap: '10px'
|
||||
}} onClick={async ()=> {
|
||||
const link = `qortal://use-group/action-join/groupid-${groupInfo?.groupId}`
|
||||
await navigator.clipboard.writeText(link);
|
||||
}}><InsertLinkIcon /> <Typography>Join Group Link</Typography></ButtonBase>
|
||||
<Typography>GroupId: {groupInfo?.groupId}</Typography>
|
||||
<Typography>GroupName: {groupInfo?.groupName}</Typography>
|
||||
<Typography>
|
||||
Number of members: {groupInfo?.memberCount}
|
||||
</Typography>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
gap: '10px',
|
||||
}}
|
||||
onClick={async () => {
|
||||
const link = `qortal://use-group/action-join/groupid-${groupInfo?.groupId}`;
|
||||
await navigator.clipboard.writeText(link);
|
||||
}}
|
||||
>
|
||||
<InsertLinkIcon /> <Typography>Join Group Link</Typography>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
{selectedGroup?.groupId && !isOwner && (
|
||||
<LoadingButton size="small" loading={isLoadingLeave} loadingPosition="start"
|
||||
variant="contained" onClick={handleLeaveGroup}>
|
||||
Leave Group
|
||||
</LoadingButton>
|
||||
)}
|
||||
<Spacer height="20px" />
|
||||
{selectedGroup?.groupId && !isOwner && (
|
||||
<LoadingButton
|
||||
size="small"
|
||||
loading={isLoadingLeave}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={handleLeaveGroup}
|
||||
>
|
||||
Leave Group
|
||||
</LoadingButton>
|
||||
)}
|
||||
</Card>
|
||||
{value === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "25px",
|
||||
maxWidth: '750px'
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
maxWidth: '750px',
|
||||
}}
|
||||
>
|
||||
<Button variant="contained" onClick={()=> getMembersWithNames(selectedGroup?.groupId)}>Load members with names</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => getMembersWithNames(selectedGroup?.groupId)}
|
||||
>
|
||||
Load members with names
|
||||
</Button>
|
||||
<Spacer height="10px" />
|
||||
<ListOfMembers
|
||||
members={membersWithNames || []}
|
||||
groupId={selectedGroup?.groupId}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
isAdmin={isAdmin}
|
||||
isOwner={isOwner}
|
||||
@ -304,64 +329,87 @@ export const ManageMembers = ({
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{value === 1 && (
|
||||
{value === 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "25px",
|
||||
maxWidth: '750px'
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
maxWidth: '750px',
|
||||
}}
|
||||
>
|
||||
<InviteMember show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
|
||||
<InviteMember
|
||||
show={show}
|
||||
groupId={selectedGroup?.groupId}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{value === 2 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "25px",
|
||||
maxWidth: '750px'
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
maxWidth: '750px',
|
||||
}}
|
||||
>
|
||||
<ListOfInvites show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
|
||||
|
||||
<ListOfInvites
|
||||
show={show}
|
||||
groupId={selectedGroup?.groupId}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{value === 3 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "25px",
|
||||
maxWidth: '750px'
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
maxWidth: '750px',
|
||||
}}
|
||||
>
|
||||
<ListOfBans show={show} groupId={selectedGroup?.groupId} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} />
|
||||
<ListOfBans
|
||||
show={show}
|
||||
groupId={selectedGroup?.groupId}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
{value === 4 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "25px",
|
||||
maxWidth: '750px'
|
||||
width: '100%',
|
||||
padding: '25px',
|
||||
maxWidth: '750px',
|
||||
}}
|
||||
>
|
||||
<ListOfJoinRequests show={show} setOpenSnack={setOpenSnack} setInfoSnack={setInfoSnack} groupId={selectedGroup?.groupId} />
|
||||
<ListOfJoinRequests
|
||||
show={show}
|
||||
setOpenSnack={setOpenSnack}
|
||||
setInfoSnack={setInfoSnack}
|
||||
groupId={selectedGroup?.groupId}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
<LoadingSnackbar
|
||||
open={isLoadingMembers}
|
||||
info={{
|
||||
message: "Loading member list with names... please wait.",
|
||||
message: 'Loading member list with names... please wait.',
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -1,14 +1,12 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import moment from 'moment'
|
||||
import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { getBaseApiReact, isMobile } from "../../App";
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import moment from 'moment';
|
||||
import { Box, ButtonBase, Collapse, Typography, useTheme } from '@mui/material';
|
||||
import { getBaseApiReact, isMobile } from '../../App';
|
||||
import MailIcon from '@mui/icons-material/Mail';
|
||||
import MailOutlineIcon from '@mui/icons-material/MailOutline';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
@ -18,262 +16,283 @@ import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread';
|
||||
|
||||
export const isLessThanOneWeekOld = (timestamp) => {
|
||||
// Current time in milliseconds
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
// One week ago in milliseconds (7 days * 24 hours * 60 minutes * 60 seconds * 1000 milliseconds)
|
||||
const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// Check if the timestamp is newer than one week ago
|
||||
return timestamp > oneWeekAgo;
|
||||
};
|
||||
|
||||
export function formatEmailDate(timestamp: number) {
|
||||
const date = moment(timestamp);
|
||||
const now = moment();
|
||||
const date = moment(timestamp);
|
||||
const now = moment();
|
||||
|
||||
if (date.isSame(now, 'day')) {
|
||||
// If the email was received today, show the time
|
||||
return date.format('h:mm A');
|
||||
} else if (date.isSame(now, 'year')) {
|
||||
// If the email was received this year, show the month and day
|
||||
return date.format('MMM D');
|
||||
} else {
|
||||
// For older emails, show the full date
|
||||
return date.format('MMM D, YYYY');
|
||||
}
|
||||
if (date.isSame(now, 'day')) {
|
||||
// If the email was received today, show the time
|
||||
return date.format('h:mm A');
|
||||
} else if (date.isSame(now, 'year')) {
|
||||
// If the email was received this year, show the month and day
|
||||
return date.format('MMM D');
|
||||
} else {
|
||||
// For older emails, show the full date
|
||||
return date.format('MMM D, YYYY');
|
||||
}
|
||||
}
|
||||
export const QMailMessages = ({userName, userAddress}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [mails, setMails] = useRecoilState(mailsAtom)
|
||||
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(qMailLastEnteredTimestampAtom)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const getMails = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const query = `qortal_qmail_${userName.slice(
|
||||
0,
|
||||
20
|
||||
)}_${userAddress.slice(-6)}_mail_`
|
||||
const response = await fetch(`${getBaseApiReact()}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=false&offset=0&reverse=true&excludeblocked=true&mode=ALL`);
|
||||
const mailData = await response.json();
|
||||
|
||||
|
||||
setMails(mailData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
export const QMailMessages = ({ userName, userAddress }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [mails, setMails] = useRecoilState(mailsAtom);
|
||||
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(
|
||||
qMailLastEnteredTimestampAtom
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const theme = useTheme();
|
||||
|
||||
}
|
||||
}, [])
|
||||
const getMails = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const query = `qortal_qmail_${userName.slice(
|
||||
0,
|
||||
20
|
||||
)}_${userAddress.slice(-6)}_mail_`;
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=false&offset=0&reverse=true&excludeblocked=true&mode=ALL`
|
||||
);
|
||||
const mailData = await response.json();
|
||||
|
||||
const getTimestamp = async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window.sendMessage("getEnteredQmailTimestamp")
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
if(response?.timestamp){
|
||||
setLastEnteredTimestamp(response?.timestamp)
|
||||
}
|
||||
setMails(mailData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getTimestamp = async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('getEnteredQmailTimestamp')
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
if (response?.timestamp) {
|
||||
setLastEnteredTimestamp(response?.timestamp);
|
||||
}
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
});
|
||||
|
||||
}
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getTimestamp()
|
||||
if(!userName || !userAddress) return
|
||||
getMails();
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
getTimestamp()
|
||||
getMails();
|
||||
}, 300000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
|
||||
}, [getMails, userName, userAddress]);
|
||||
useEffect(() => {
|
||||
getTimestamp();
|
||||
if (!userName || !userAddress) return;
|
||||
getMails();
|
||||
|
||||
const anyUnread = useMemo(()=> {
|
||||
let unread = false
|
||||
|
||||
mails.forEach((mail)=> {
|
||||
if(!lastEnteredTimestamp && isLessThanOneWeekOld(mail?.created) || (lastEnteredTimestamp && isLessThanOneWeekOld(mail?.created) && lastEnteredTimestamp < mail?.created)){
|
||||
unread = true
|
||||
}
|
||||
})
|
||||
return unread
|
||||
}, [mails, lastEnteredTimestamp])
|
||||
const interval = setInterval(() => {
|
||||
getTimestamp();
|
||||
getMails();
|
||||
}, 300000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [getMails, userName, userAddress]);
|
||||
|
||||
const anyUnread = useMemo(() => {
|
||||
let unread = false;
|
||||
|
||||
mails.forEach((mail) => {
|
||||
if (
|
||||
(!lastEnteredTimestamp && isLessThanOneWeekOld(mail?.created)) ||
|
||||
(lastEnteredTimestamp &&
|
||||
isLessThanOneWeekOld(mail?.created) &&
|
||||
lastEnteredTimestamp < mail?.created)
|
||||
) {
|
||||
unread = true;
|
||||
}
|
||||
});
|
||||
return unread;
|
||||
}, [mails, lastEnteredTimestamp]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: "322px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: '10px',
|
||||
padding: "0px 20px",
|
||||
justifyContent: 'flex-start'
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onClick={()=> setIsExpanded((prev)=> !prev)}
|
||||
>
|
||||
<Typography
|
||||
<ButtonBase
|
||||
sx={{
|
||||
fontSize: "1rem",
|
||||
width: '322px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '10px',
|
||||
padding: '0px 20px',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
Latest Q-Mails
|
||||
</Typography>
|
||||
<MarkEmailUnreadIcon sx={{
|
||||
color: anyUnread ? 'var(--unread)' : 'white'
|
||||
}}/>
|
||||
{isExpanded ? <ExpandLessIcon sx={{
|
||||
marginLeft: 'auto'
|
||||
}} /> : (
|
||||
<ExpandMoreIcon sx={{
|
||||
color: anyUnread ? 'var(--unread)' : 'white',
|
||||
marginLeft: 'auto'
|
||||
}} />
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
className="scrollable-container"
|
||||
sx={{
|
||||
width: "322px",
|
||||
height: isMobile ? "165px" : "250px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
bgcolor: "background.paper",
|
||||
padding: "20px",
|
||||
borderRadius: "19px",
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
{loading && mails.length === 0 && (
|
||||
<Box
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
>
|
||||
Latest Q-Mails
|
||||
</Typography>
|
||||
<MarkEmailUnreadIcon
|
||||
sx={{
|
||||
color: anyUnread ? 'var(--unread)' : 'white',
|
||||
}}
|
||||
/>
|
||||
{isExpanded ? (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
/>
|
||||
) : (
|
||||
<ExpandMoreIcon
|
||||
sx={{
|
||||
color: anyUnread ? 'var(--unread)' : 'white',
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!loading && mails.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
</ButtonBase>
|
||||
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
className="scrollable-container"
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.paper,
|
||||
borderRadius: '19px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '165px' : '250px',
|
||||
overflow: 'auto',
|
||||
padding: '20px',
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
{loading && mails.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "11px",
|
||||
fontWeight: 400,
|
||||
color: 'rgba(255, 255, 255, 0.2)'
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<List sx={{ width: "100%", maxWidth: 360 }}>
|
||||
{mails?.map((mail)=> {
|
||||
return (
|
||||
<ListItem
|
||||
|
||||
disablePadding
|
||||
sx={{
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
onClick={()=> {
|
||||
executeEvent("addTab", { data: { service: 'APP', name: 'q-mail' } });
|
||||
executeEvent("open-apps-mode", { });
|
||||
setLastEnteredTimestamp(Date.now())
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{!loading && mails.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 400,
|
||||
color: theme.palette.primary,
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<List sx={{ width: '100%', maxWidth: 360 }}>
|
||||
{mails?.map((mail) => {
|
||||
return (
|
||||
<ListItem
|
||||
disablePadding
|
||||
sx={{
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
onClick={() => {
|
||||
executeEvent('addTab', {
|
||||
data: { service: 'APP', name: 'q-mail' },
|
||||
});
|
||||
executeEvent('open-apps-mode', {});
|
||||
setLastEnteredTimestamp(Date.now());
|
||||
}}
|
||||
>
|
||||
<ListItemButton
|
||||
sx={{
|
||||
padding: '0px',
|
||||
}}
|
||||
disableRipple
|
||||
role={undefined}
|
||||
dense
|
||||
>
|
||||
<ListItemButton
|
||||
<ListItemText
|
||||
sx={{
|
||||
padding: "0px",
|
||||
'& .MuiTypography-root': {
|
||||
fontSize: '13px',
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
primary={`From: ${mail?.name}`}
|
||||
secondary={`${formatEmailDate(mail?.created)}`}
|
||||
/>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
disableRipple
|
||||
role={undefined}
|
||||
dense
|
||||
>
|
||||
<ListItemText
|
||||
sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "13px",
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
primary={`From: ${mail?.name}`}
|
||||
secondary={`${formatEmailDate(mail?.created)}`}
|
||||
/>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
{!lastEnteredTimestamp && isLessThanOneWeekOld(mail?.created) ? (
|
||||
<MailIcon sx={{
|
||||
color: 'var(--unread)'
|
||||
}} />
|
||||
) : !lastEnteredTimestamp ? (
|
||||
<MailOutlineIcon sx={{
|
||||
color: 'white'
|
||||
}} />
|
||||
): (lastEnteredTimestamp < mail?.created) && isLessThanOneWeekOld(mail?.created) ? (
|
||||
<MailIcon sx={{
|
||||
color: 'var(--unread)'
|
||||
}} />
|
||||
) : (
|
||||
<MailOutlineIcon sx={{
|
||||
color: 'white'
|
||||
}} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
)
|
||||
{!lastEnteredTimestamp &&
|
||||
isLessThanOneWeekOld(mail?.created) ? (
|
||||
<MailIcon
|
||||
sx={{
|
||||
color: 'var(--unread)',
|
||||
}}
|
||||
/>
|
||||
) : !lastEnteredTimestamp ? (
|
||||
<MailOutlineIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
) : lastEnteredTimestamp < mail?.created &&
|
||||
isLessThanOneWeekOld(mail?.created) ? (
|
||||
<MailIcon
|
||||
sx={{
|
||||
color: 'var(--unread)',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MailOutlineIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
</List>
|
||||
|
||||
|
||||
</List>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,37 +1,20 @@
|
||||
import * as React from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Slide from "@mui/material/Slide";
|
||||
import { TransitionProps } from "@mui/material/transitions";
|
||||
import ListOfMembers from "./ListOfMembers";
|
||||
import { InviteMember } from "./InviteMember";
|
||||
import { ListOfInvites } from "./ListOfInvites";
|
||||
import { ListOfBans } from "./ListOfBans";
|
||||
import { ListOfJoinRequests } from "./ListOfJoinRequests";
|
||||
import { Box, FormControlLabel, Switch, Tab, Tabs, styled } from "@mui/material";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { MyContext, isMobile } from "../../App";
|
||||
import { getGroupMembers, getNames } from "./Group";
|
||||
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
|
||||
import { getFee } from "../../background";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||
import { enabledDevModeAtom } from "../../atoms/global";
|
||||
import { useRecoilState } from "recoil";
|
||||
import * as React from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
|
||||
import { enabledDevModeAtom } from '../../atoms/global';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -49,13 +32,13 @@ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
|
||||
},
|
||||
'&::before': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
||||
theme.palette.getContrastText(theme.palette.primary.main),
|
||||
theme.palette.getContrastText(theme.palette.primary.main)
|
||||
)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`,
|
||||
left: 12,
|
||||
},
|
||||
'&::after': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
||||
theme.palette.getContrastText(theme.palette.primary.main),
|
||||
theme.palette.getContrastText(theme.palette.primary.main)
|
||||
)}" d="M19,13H5V11H19V13Z" /></svg>')`,
|
||||
right: 12,
|
||||
},
|
||||
@ -77,35 +60,34 @@ const Transition = React.forwardRef(function Transition(
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export const Settings = ({
|
||||
address,
|
||||
open,
|
||||
setOpen,
|
||||
}) => {
|
||||
export const Settings = ({ address, open, setOpen }) => {
|
||||
const [checked, setChecked] = React.useState(false);
|
||||
const [isEnabledDevMode, setIsEnabledDevMode] = useRecoilState(enabledDevModeAtom)
|
||||
|
||||
|
||||
const [isEnabledDevMode, setIsEnabledDevMode] =
|
||||
useRecoilState(enabledDevModeAtom);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setChecked(event.target.checked);
|
||||
window.sendMessage("addUserSettings", {
|
||||
keyValue: {
|
||||
key: 'disable-push-notifications',
|
||||
value: event.target.checked,
|
||||
},
|
||||
})
|
||||
window
|
||||
.sendMessage('addUserSettings', {
|
||||
keyValue: {
|
||||
key: 'disable-push-notifications',
|
||||
value: event.target.checked,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.error) {
|
||||
console.error("Error adding user settings:", response.error);
|
||||
console.error('Error adding user settings:', response.error);
|
||||
} else {
|
||||
console.log("User settings added successfully");
|
||||
console.log('User settings added successfully');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to add user settings:", error.message || "An error occurred");
|
||||
console.error(
|
||||
'Failed to add user settings:',
|
||||
error.message || 'An error occurred'
|
||||
);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@ -115,9 +97,10 @@ export const Settings = ({
|
||||
const getUserSettings = async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window.sendMessage("getUserSettings", {
|
||||
key: "disable-push-notifications",
|
||||
})
|
||||
window
|
||||
.sendMessage('getUserSettings', {
|
||||
key: 'disable-push-notifications',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setChecked(response || false);
|
||||
@ -127,12 +110,11 @@ export const Settings = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
console.log('error', error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -140,8 +122,6 @@ export const Settings = ({
|
||||
getUserSettings();
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Dialog
|
||||
@ -150,11 +130,14 @@ export const Settings = ({
|
||||
onClose={handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar sx={{ position: "relative", bgcolor: "#232428" }}>
|
||||
<AppBar
|
||||
sx={{ position: 'relative', bgcolor: theme.palette.background }}
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
|
||||
General Settings
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
@ -165,21 +148,22 @@ export const Settings = ({
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: "#27282c",
|
||||
bgcolor: theme.palette.background,
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
color: "white",
|
||||
padding: "20px",
|
||||
overflowY: 'auto',
|
||||
color: theme.palette.text.primary,
|
||||
padding: '20px',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
gap: '20px'
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
sx={{
|
||||
color: "white",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
control={
|
||||
<LocalNodeSwitch checked={checked} onChange={handleChange} />
|
||||
@ -188,17 +172,23 @@ export const Settings = ({
|
||||
/>
|
||||
{window?.electronAPI && (
|
||||
<FormControlLabel
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
control={
|
||||
<LocalNodeSwitch checked={isEnabledDevMode} onChange={(e)=> {
|
||||
setIsEnabledDevMode(e.target.checked)
|
||||
localStorage.setItem('isEnabledDevMode', JSON.stringify(e.target.checked))
|
||||
}} />
|
||||
}
|
||||
label="Enable dev mode"
|
||||
/>
|
||||
sx={{
|
||||
color: 'white',
|
||||
}}
|
||||
control={
|
||||
<LocalNodeSwitch
|
||||
checked={isEnabledDevMode}
|
||||
onChange={(e) => {
|
||||
setIsEnabledDevMode(e.target.checked);
|
||||
localStorage.setItem(
|
||||
'isEnabledDevMode',
|
||||
JSON.stringify(e.target.checked)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Enable dev mode"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Dialog>
|
||||
|
@ -1,23 +1,17 @@
|
||||
import { Box, ButtonBase, Divider, Typography } from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import AppViewerContainer from "../Apps/AppViewerContainer";
|
||||
import { Box, ButtonBase, Divider, Typography } from '@mui/material';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import AppViewerContainer from '../Apps/AppViewerContainer';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from "../../utils/events";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { navigationControllerAtom } from "../../atoms/global";
|
||||
import { AppsNavBarLeft, AppsNavBarParent } from "../Apps/Apps-styles";
|
||||
import NavBack from "../../assets/svgs/NavBack.svg";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
} from '../../utils/events';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { navigationControllerAtom } from '../../atoms/global';
|
||||
import { AppsNavBarLeft, AppsNavBarParent } from '../Apps/Apps-styles';
|
||||
import { NavBack } from '../../assets/Icons/NavBack.tsx';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
|
||||
export const WalletsAppWrapper = () => {
|
||||
const iframeRef = useRef(null);
|
||||
@ -26,10 +20,10 @@ export const WalletsAppWrapper = () => {
|
||||
navigationControllerAtom
|
||||
);
|
||||
const [selectedTab, setSelectedTab] = useState({
|
||||
tabId: "5558589",
|
||||
name: "Q-Wallets",
|
||||
service: "APP",
|
||||
path: 'qortal?authOnMount=true'
|
||||
tabId: '5558589',
|
||||
name: 'Q-Wallets',
|
||||
service: 'APP',
|
||||
path: 'qortal?authOnMount=true',
|
||||
});
|
||||
|
||||
const isDisableBackButton = useMemo(() => {
|
||||
@ -48,64 +42,64 @@ export const WalletsAppWrapper = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("openWalletsApp", openWalletsAppFunc);
|
||||
subscribeToEvent('openWalletsApp', openWalletsAppFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("openWalletsApp", openWalletsAppFunc);
|
||||
unsubscribeFromEvent('openWalletsApp', openWalletsAppFunc);
|
||||
};
|
||||
}, [openWalletsAppFunc]);
|
||||
|
||||
const handleClose = ()=> {
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
iframeRef.current = null
|
||||
}
|
||||
iframeRef.current = null;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "fixed",
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
backgroundColor: "#27282c",
|
||||
position: 'fixed',
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
backgroundColor: '#27282c', // TODO: set color theme
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
overflow: "hidden",
|
||||
borderTopLeftRadius: "10px",
|
||||
borderTopRightRadius: "10px",
|
||||
overflow: 'hidden',
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
boxShadow: 4,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "5px",
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
|
||||
justifyContent: "space-between",
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Wallets</Typography>
|
||||
<ButtonBase
|
||||
onClick={handleClose}
|
||||
>
|
||||
<ButtonBase onClick={handleClose}>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
color: "white",
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<AppViewerContainer
|
||||
customHeight="calc(100% - 40px - 60px)"
|
||||
app={selectedTab}
|
||||
@ -114,9 +108,11 @@ export const WalletsAppWrapper = () => {
|
||||
skipAuth={true}
|
||||
/>
|
||||
<AppsNavBarParent>
|
||||
<AppsNavBarLeft sx={{
|
||||
gap: '25px'
|
||||
}}>
|
||||
<AppsNavBarLeft
|
||||
sx={{
|
||||
gap: '25px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {});
|
||||
@ -124,31 +120,30 @@ export const WalletsAppWrapper = () => {
|
||||
disabled={isDisableBackButton}
|
||||
sx={{
|
||||
opacity: !isDisableBackButton ? 1 : 0.1,
|
||||
cursor: !isDisableBackButton ? "pointer" : "default",
|
||||
cursor: !isDisableBackButton ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
<img src={NavBack} />
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
<ButtonBase onClick={() => {
|
||||
if (selectedTab?.refreshFunc) {
|
||||
selectedTab.refreshFunc(selectedTab?.tabId);
|
||||
|
||||
} else {
|
||||
executeEvent("refreshApp", {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}}>
|
||||
<RefreshIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: "rgba(250, 250, 250, 0.5)",
|
||||
height: '30px',
|
||||
width: 'auto'
|
||||
}}
|
||||
/>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
if (selectedTab?.refreshFunc) {
|
||||
selectedTab.refreshFunc(selectedTab?.tabId);
|
||||
} else {
|
||||
executeEvent('refreshApp', {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
height={20}
|
||||
sx={{
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
height: '30px',
|
||||
width: 'auto',
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</AppsNavBarLeft>
|
||||
</AppsNavBarParent>
|
||||
|
@ -1,22 +1,29 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import Logo2 from "../assets/svgs/Logo2.svg";
|
||||
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from "../App";
|
||||
import { Avatar, Box, Button, ButtonBase, Popover, Typography } from "@mui/material";
|
||||
import { Spacer } from "../common/Spacer";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import { getFee } from "../background";
|
||||
import { fileToBase64 } from "../utils/fileReading";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import Logo2 from '../assets/svgs/Logo2.svg';
|
||||
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Popover,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Spacer } from '../common/Spacer';
|
||||
import ImageUploader from '../common/ImageUploader';
|
||||
import { getFee } from '../background';
|
||||
import { fileToBase64 } from '../utils/fileReading';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
|
||||
export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
|
||||
const [hasAvatar, setHasAvatar] = useState(false);
|
||||
const [avatarFile, setAvatarFile] = useState(null);
|
||||
const [tempAvatar, setTempAvatar] = useState(null)
|
||||
const [tempAvatar, setTempAvatar] = useState(null);
|
||||
const { show } = useContext(MyContext);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// Handle child element click to open Popover
|
||||
const handleChildClick = (event) => {
|
||||
event.stopPropagation(); // Prevent parent onClick from firing
|
||||
@ -37,93 +44,105 @@ const [isLoading, setIsLoading] = useState(false)
|
||||
const identifier = `qortal_avatar`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=THUMBNAIL&identifier=${identifier}&limit=1&name=${myName}&includemetadata=false&prefix=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
if (responseData?.length > 0) {
|
||||
setHasAvatar(true);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!myName) return;
|
||||
checkIfAvatarExists();
|
||||
}, [myName]);
|
||||
|
||||
|
||||
const publishAvatar = async ()=> {
|
||||
const publishAvatar = async () => {
|
||||
try {
|
||||
const fee = await getFee('ARBITRARY')
|
||||
if(+balance < +fee.fee) throw new Error(`Publishing an Avatar requires ${fee.fee}`)
|
||||
await show({
|
||||
message: "Would you like to publish an avatar?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
setIsLoading(true);
|
||||
const avatarBase64 = await fileToBase64(avatarFile)
|
||||
await new Promise((res, rej) => {
|
||||
window.sendMessage("publishOnQDN", {
|
||||
data: avatarBase64,
|
||||
identifier: "qortal_avatar",
|
||||
service: "THUMBNAIL",
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
});
|
||||
|
||||
});
|
||||
setAvatarFile(null);
|
||||
setTempAvatar(`data:image/webp;base64,${avatarBase64}`)
|
||||
handleClose()
|
||||
const fee = await getFee('ARBITRARY');
|
||||
if (+balance < +fee.fee)
|
||||
throw new Error(`Publishing an Avatar requires ${fee.fee}`);
|
||||
await show({
|
||||
message: 'Would you like to publish an avatar?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoading(true);
|
||||
const avatarBase64 = await fileToBase64(avatarFile);
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('publishOnQDN', {
|
||||
data: avatarBase64,
|
||||
identifier: 'qortal_avatar',
|
||||
service: 'THUMBNAIL',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
setAvatarFile(null);
|
||||
setTempAvatar(`data:image/webp;base64,${avatarBase64}`);
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
if (error?.message) {
|
||||
setOpenSnack(true)
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error?.message,
|
||||
});
|
||||
}
|
||||
setOpenSnack(true);
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error?.message,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(tempAvatar){
|
||||
if (tempAvatar) {
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
<>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: '138px',
|
||||
width: '138px',
|
||||
}}
|
||||
src={tempAvatar}
|
||||
alt={myName}
|
||||
>
|
||||
{myName?.charAt(0)}
|
||||
</Avatar>
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: "138px",
|
||||
width: "138px",
|
||||
fontSize: '12px',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
src={tempAvatar}
|
||||
alt={myName}
|
||||
>
|
||||
{myName?.charAt(0)}
|
||||
</Avatar>
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
change avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
|
||||
</>
|
||||
);
|
||||
change avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
setAvatarFile={setAvatarFile}
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
handleClose={handleClose}
|
||||
publishAvatar={publishAvatar}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasAvatar) {
|
||||
@ -131,8 +150,8 @@ const [isLoading, setIsLoading] = useState(false)
|
||||
<>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "138px",
|
||||
width: "138px",
|
||||
height: '138px',
|
||||
width: '138px',
|
||||
}}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${myName}/qortal_avatar?async=true`}
|
||||
alt={myName}
|
||||
@ -142,14 +161,24 @@ const [isLoading, setIsLoading] = useState(false)
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
fontSize: '12px',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
change avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
setAvatarFile={setAvatarFile}
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
handleClose={handleClose}
|
||||
publishAvatar={publishAvatar}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -160,42 +189,61 @@ const [isLoading, setIsLoading] = useState(false)
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
fontSize: '12px',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
set avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<PopoverComp myName={myName} avatarFile={avatarFile} setAvatarFile={setAvatarFile} id={id} open={open} anchorEl={anchorEl} handleClose={handleClose} publishAvatar={publishAvatar} isLoading={isLoading} />
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
setAvatarFile={setAvatarFile}
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
handleClose={handleClose}
|
||||
publishAvatar={publishAvatar}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const PopoverComp = ({avatarFile, setAvatarFile, id, open, anchorEl, handleClose, publishAvatar, isLoading, myName}) => {
|
||||
return (
|
||||
<Popover
|
||||
const PopoverComp = ({
|
||||
avatarFile,
|
||||
setAvatarFile,
|
||||
id,
|
||||
open,
|
||||
anchorEl,
|
||||
handleClose,
|
||||
publishAvatar,
|
||||
isLoading,
|
||||
myName,
|
||||
}) => {
|
||||
return (
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose} // Close popover on click outside
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2, display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
(500 KB max. for GIFS){" "}
|
||||
(500 KB max. for GIFS){' '}
|
||||
</Typography>
|
||||
<ImageUploader onPick={(file) => setAvatarFile(file)}>
|
||||
<Button variant="contained">Choose Image</Button>
|
||||
@ -203,23 +251,34 @@ const PopoverComp = ({avatarFile, setAvatarFile, id, open, anchorEl, handleClose
|
||||
{avatarFile?.name}
|
||||
<Spacer height="25px" />
|
||||
{!myName && (
|
||||
<Box sx={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<ErrorIcon sx={{
|
||||
color: 'white'
|
||||
}} />
|
||||
<Typography>A registered name is required to set an avatar</Typography>
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ErrorIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
<Typography>
|
||||
A registered name is required to set an avatar
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Spacer height="25px" />
|
||||
<LoadingButton loading={isLoading} disabled={!avatarFile || !myName} onClick={publishAvatar} variant="contained">
|
||||
|
||||
<Spacer height="25px" />
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
disabled={!avatarFile || !myName}
|
||||
onClick={publishAvatar}
|
||||
variant="contained"
|
||||
>
|
||||
Publish avatar
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
)
|
||||
};
|
||||
);
|
||||
};
|
||||
|
@ -9,31 +9,20 @@ import {
|
||||
DialogTitle,
|
||||
Divider,
|
||||
IconButton,
|
||||
InputBase,
|
||||
InputLabel,
|
||||
Snackbar,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import {
|
||||
executeEvent,
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../utils/events';
|
||||
import { getFee, getNameOrAddress } from '../../background';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { AddressBox } from '../../styles/App-styles';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import Copy from '../../assets/svgs/Copy.svg';
|
||||
import { Loader } from '../Loader';
|
||||
import { FidgetSpinner } from 'react-loader-spinner';
|
||||
import { useModal } from '../../common/useModal';
|
||||
|
||||
@ -56,15 +45,18 @@ export const Minting = ({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { show: showKey, message } = useModal();
|
||||
const { isShow: isShowNext, onOk, show: showNext } = useModal();
|
||||
const theme = useTheme();
|
||||
|
||||
const [info, setInfo] = useState(null);
|
||||
const [names, setNames] = useState({});
|
||||
const [accountInfos, setAccountInfos] = useState({});
|
||||
const [showWaitDialog, setShowWaitDialog] = useState(false);
|
||||
|
||||
const isPartOfMintingGroup = useMemo(() => {
|
||||
if (groups?.length === 0) return false;
|
||||
return !!groups?.find((item) => item?.groupId?.toString() === '694');
|
||||
}, [groups]);
|
||||
|
||||
const getMintingAccounts = useCallback(async () => {
|
||||
try {
|
||||
const url = `${getBaseApiReact()}/admin/mintingaccounts`;
|
||||
@ -74,7 +66,9 @@ export const Minting = ({
|
||||
}
|
||||
const data = await response.json();
|
||||
setMintingAccounts(data);
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const accountIsMinting = useMemo(() => {
|
||||
@ -105,7 +99,7 @@ export const Minting = ({
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// error
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -131,6 +125,7 @@ export const Minting = ({
|
||||
setAccountInfo(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (!others) {
|
||||
setIsLoading(false);
|
||||
@ -199,7 +194,9 @@ export const Minting = ({
|
||||
const data = await response.json();
|
||||
setRewardShares(data);
|
||||
return data;
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const addMintingAccount = useCallback(async (val) => {
|
||||
@ -250,7 +247,6 @@ export const Minting = ({
|
||||
window
|
||||
.sendMessage(
|
||||
'ADMIN_ACTION',
|
||||
|
||||
{
|
||||
type: 'removemintingaccount',
|
||||
value: val,
|
||||
@ -354,7 +350,6 @@ export const Minting = ({
|
||||
if (findRewardShare) {
|
||||
return true; // Exit early if found
|
||||
}
|
||||
|
||||
await sleep(pollingInterval); // Wait before the next poll
|
||||
}
|
||||
|
||||
@ -507,7 +502,7 @@ export const Minting = ({
|
||||
|
||||
const _blocksNeed = () => {
|
||||
if (accountInfo?.level === 0) {
|
||||
return 7200;
|
||||
return 7200; // TODO manage these magic numbers in a proper location
|
||||
} else if (accountInfo?.level === 1) {
|
||||
return 72000;
|
||||
} else if (accountInfo?.level === 2) {
|
||||
@ -558,11 +553,11 @@ export const Minting = ({
|
||||
fullScreen
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
height: '100vh',
|
||||
margin: 0,
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
overflow: 'hidden', // Prevent scrollbars
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
@ -579,6 +574,7 @@ export const Minting = ({
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
<DialogContent
|
||||
sx={{
|
||||
position: 'relative',
|
||||
@ -587,37 +583,40 @@ export const Minting = ({
|
||||
{isLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
<FidgetSpinner
|
||||
visible={true}
|
||||
height="80"
|
||||
width="80"
|
||||
ariaLabel="fidget-spinner-loading"
|
||||
wrapperStyle={{}}
|
||||
height="80"
|
||||
visible={true}
|
||||
width="80"
|
||||
wrapperClass="fidget-spinner-wrapper"
|
||||
wrapperStyle={{}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<Typography>Account: {handleNames(accountInfo?.address)}</Typography>
|
||||
|
||||
<Typography>Level: {accountInfo?.level}</Typography>
|
||||
|
||||
<Typography>
|
||||
blocks remaining until next level: {_levelUpBlocks()}
|
||||
</Typography>
|
||||
|
||||
<Typography>
|
||||
This node is minting: {nodeInfos?.isMintingPossible?.toString()}
|
||||
</Typography>
|
||||
@ -626,11 +625,11 @@ export const Minting = ({
|
||||
{isPartOfMintingGroup && !accountIsMinting && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@ -670,7 +669,7 @@ export const Minting = ({
|
||||
)}
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
@ -705,14 +704,14 @@ export const Minting = ({
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
maxWidth: '90%',
|
||||
opacity: 0.7,
|
||||
width: '200px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
color: theme.palette.text.primary,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
@ -723,7 +722,9 @@ export const Minting = ({
|
||||
>
|
||||
Remove minting account
|
||||
</Button>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Spacer height="10px" />
|
||||
</Box>
|
||||
))}
|
||||
@ -740,7 +741,7 @@ export const Minting = ({
|
||||
{!isPartOfMintingGroup && (
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
@ -764,7 +765,7 @@ export const Minting = ({
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: 'var(--green)',
|
||||
color: 'black',
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
|
||||
@ -798,6 +799,7 @@ export const Minting = ({
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{isShowNext ? 'Confirmed' : 'Please Wait'}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
{!isShowNext && (
|
||||
<Typography>
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { useMemo } from 'react';
|
||||
import QMailLogo from '../assets/QMailLogo.png';
|
||||
import EmailIcon from '@mui/icons-material/Email';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global';
|
||||
import { isLessThanOneWeekOld } from './Group/QMailMessages';
|
||||
import { ButtonBase, Tooltip } from '@mui/material';
|
||||
import { ButtonBase, Tooltip, useTheme } from '@mui/material';
|
||||
import { executeEvent } from '../utils/events';
|
||||
import { Mail } from '@mui/icons-material';
|
||||
|
||||
export const QMailStatus = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(
|
||||
qMailLastEnteredTimestampAtom
|
||||
);
|
||||
@ -24,6 +27,7 @@ export const QMailStatus = () => {
|
||||
return true;
|
||||
return false;
|
||||
}, [lastEnteredTimestamp, mails]);
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
@ -38,21 +42,27 @@ export const QMailStatus = () => {
|
||||
{hasNewMail && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: '-7px',
|
||||
right: '-7px',
|
||||
backgroundColor: 'var(--unread)',
|
||||
height: '15px',
|
||||
width: '15px',
|
||||
borderRadius: '50%',
|
||||
height: '15px',
|
||||
outline: '1px solid white',
|
||||
position: 'absolute',
|
||||
right: '-7px',
|
||||
top: '-7px',
|
||||
width: '15px',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
title={
|
||||
<span style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}>
|
||||
<span
|
||||
style={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
Q-MAIL
|
||||
</span>
|
||||
}
|
||||
@ -62,18 +72,18 @@ export const QMailStatus = () => {
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: '#444444',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img style={{ width: '24px', height: 'auto' }} src={QMailLogo} />
|
||||
<Mail />
|
||||
</Tooltip>
|
||||
</ButtonBase>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, CircularProgress, useTheme } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
CustomButton,
|
||||
CustomInput,
|
||||
@ -13,6 +13,7 @@ import { ErrorText } from './ErrorText/ErrorText';
|
||||
import { getFee } from '../background';
|
||||
|
||||
export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
const theme = useTheme();
|
||||
const [paymentTo, setPaymentTo] = useState<string>(defaultPaymentTo);
|
||||
const [paymentAmount, setPaymentAmount] = useState<number>(0);
|
||||
const [paymentPassword, setPaymentPassword] = useState<string>('');
|
||||
@ -42,7 +43,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
message: `Would you like to transfer ${Number(paymentAmount)} QORT?`,
|
||||
paymentFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
setIsLoadingSendCoin(true);
|
||||
|
||||
window
|
||||
.sendMessage('sendCoin', {
|
||||
amount: Number(paymentAmount),
|
||||
@ -62,65 +65,76 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
setIsLoadingSendCoin(false);
|
||||
});
|
||||
} catch (error) {
|
||||
// error
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'start',
|
||||
lineHeight: '24px',
|
||||
fontSize: '20px',
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
textAlign: 'start',
|
||||
}}
|
||||
>
|
||||
Transfer QORT
|
||||
</TextP>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'start',
|
||||
lineHeight: '16px',
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '20px',
|
||||
fontWeight: 600,
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
lineHeight: '16px',
|
||||
textAlign: 'start',
|
||||
}}
|
||||
>
|
||||
Balance:
|
||||
</TextP>
|
||||
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'start',
|
||||
lineHeight: '24px',
|
||||
fontSize: '20px',
|
||||
fontWeight: 700,
|
||||
lineHeight: '24px',
|
||||
textAlign: 'start',
|
||||
}}
|
||||
>
|
||||
{balance?.toFixed(2)} QORT
|
||||
</TextP>
|
||||
</Box>
|
||||
|
||||
<Spacer height="35px" />
|
||||
|
||||
<Box>
|
||||
<CustomLabel htmlFor="standard-adornment-name">To</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<CustomInput
|
||||
id="standard-adornment-name"
|
||||
value={paymentTo}
|
||||
onChange={(e) => setPaymentTo(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<CustomLabel htmlFor="standard-adornment-amount">Amount</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<BoundedNumericTextField
|
||||
value={paymentAmount}
|
||||
minValue={0}
|
||||
@ -130,11 +144,15 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
allowNegatives={false}
|
||||
afterChange={(e: string) => setPaymentAmount(+e)}
|
||||
/>
|
||||
|
||||
<Spacer height="6px" />
|
||||
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
Confirm Wallet Password
|
||||
</CustomLabel>
|
||||
|
||||
<Spacer height="5px" />
|
||||
|
||||
<PasswordField
|
||||
id="standard-adornment-password"
|
||||
value={paymentPassword}
|
||||
@ -142,10 +160,14 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<ErrorText>{sendPaymentError}</ErrorText>
|
||||
{/* <Typography>{sendPaymentSuccess}</Typography> */}
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
<CustomButton
|
||||
sx={{
|
||||
cursor: isLoadingSendCoin ? 'default' : 'pointer',
|
||||
@ -159,7 +181,7 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
<CircularProgress
|
||||
size={16}
|
||||
sx={{
|
||||
color: 'white',
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
import isEqual from "lodash/isEqual"; // Import deep comparison utility
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import isEqual from 'lodash/isEqual'; // Import deep comparison utility
|
||||
import {
|
||||
canSaveSettingToQdnAtom,
|
||||
hasSettingsChangedAtom,
|
||||
@ -9,35 +9,35 @@ import {
|
||||
settingsLocalLastUpdatedAtom,
|
||||
settingsQDNLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from "../../atoms/global";
|
||||
import { Box, Button, ButtonBase, Popover, Typography } from "@mui/material";
|
||||
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
||||
import { MyContext } from "../../App";
|
||||
import { getFee } from "../../background";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { SaveIcon } from "../../assets/svgs/SaveIcon";
|
||||
import { IconWrapper } from "../Desktop/DesktopFooter";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { saveToLocalStorage } from "../Apps/AppsNavBar";
|
||||
import { decryptData, encryptData } from "../../qortalRequests/get";
|
||||
import { saveFileToDiskGeneric } from "../../utils/generateWallet/generateWallet";
|
||||
} from '../../atoms/global';
|
||||
import { Box, Button, ButtonBase, Popover, Typography } from '@mui/material';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import { MyContext } from '../../App';
|
||||
import { getFee } from '../../background';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { SaveIcon } from '../../assets/Icons/SaveIcon';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { saveToLocalStorage } from '../Apps/AppsNavBar';
|
||||
import { decryptData, encryptData } from '../../qortalRequests/get';
|
||||
import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet';
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
uint8ArrayToObject,
|
||||
} from "../../backgroundFunctions/encryption";
|
||||
} from '../../backgroundFunctions/encryption';
|
||||
|
||||
export const handleImportClick = async () => {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".base64,.txt";
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.base64,.txt';
|
||||
|
||||
// Create a promise to handle file selection and reading synchronously
|
||||
return await new Promise((resolve, reject) => {
|
||||
fileInput.onchange = () => {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) {
|
||||
reject(new Error("No file selected"));
|
||||
reject(new Error('No file selected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ export const handleImportClick = async () => {
|
||||
resolve(e.target.result); // Resolve with the file content
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Error reading file"));
|
||||
reject(new Error('Error reading file'));
|
||||
};
|
||||
|
||||
reader.readAsText(file); // Read the file as text (Base64 string)
|
||||
@ -124,7 +124,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
const encryptData = await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage(
|
||||
"ENCRYPT_DATA",
|
||||
'ENCRYPT_DATA',
|
||||
{
|
||||
data64,
|
||||
},
|
||||
@ -139,23 +139,23 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed qortalRequest", error);
|
||||
console.error('Failed qortalRequest', error);
|
||||
});
|
||||
});
|
||||
if (encryptData && !encryptData?.error) {
|
||||
const fee = await getFee("ARBITRARY");
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
await show({
|
||||
message:
|
||||
"Would you like to publish your settings to QDN (encrypted) ?",
|
||||
publishFee: fee.fee + " QORT",
|
||||
'Would you like to publish your settings to QDN (encrypted) ?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
const response = await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage("publishOnQDN", {
|
||||
.sendMessage('publishOnQDN', {
|
||||
data: encryptData,
|
||||
identifier: "ext_saved_settings",
|
||||
service: "DOCUMENT_PRIVATE",
|
||||
identifier: 'ext_saved_settings',
|
||||
service: 'DOCUMENT_PRIVATE',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
@ -165,15 +165,15 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || "An error occurred");
|
||||
rej(error.message || 'An error occurred');
|
||||
});
|
||||
});
|
||||
if (response?.identifier) {
|
||||
setOldPinnedApps(pinnedApps);
|
||||
setSettingsQdnLastUpdated(Date.now());
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Sucessfully published to QDN",
|
||||
type: 'success',
|
||||
message: 'Sucessfully published to QDN',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setAnchorEl(null);
|
||||
@ -181,8 +181,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
}
|
||||
} catch (error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to save to QDN",
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to save to QDN',
|
||||
});
|
||||
setOpenSnack(true);
|
||||
} finally {
|
||||
@ -196,7 +196,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
|
||||
const revertChanges = () => {
|
||||
setPinnedApps(oldPinnedApps);
|
||||
saveToLocalStorage("ext_saved_settings", "sortablePinnedApps", null);
|
||||
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', null);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
@ -218,11 +218,11 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
selected={false}
|
||||
>
|
||||
<SaveIcon
|
||||
color={hasChanged && !isLoading ? "#5EB049" : undefined}
|
||||
color={hasChanged && !isLoading ? '#5EB049' : undefined}
|
||||
/>
|
||||
</IconWrapper>
|
||||
) : (
|
||||
<SaveIcon color={hasChanged && !isLoading ? "#5EB049" : undefined} />
|
||||
<SaveIcon color={hasChanged && !isLoading ? '#5EB049' : undefined} />
|
||||
)}
|
||||
</ButtonBase>
|
||||
<Popover
|
||||
@ -230,41 +230,41 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => setAnchorEl(null)} // Close popover on click outside
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
sx={{
|
||||
width: "300px",
|
||||
maxWidth: "90%",
|
||||
maxHeight: "80%",
|
||||
overflow: "auto",
|
||||
width: '300px',
|
||||
maxWidth: '90%',
|
||||
maxHeight: '80%',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{isUsingImportExportSettings && (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "15px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: '15px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
You are using the export/import way of saving settings.
|
||||
@ -274,8 +274,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
size="small"
|
||||
onClick={() => {
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings_import_export",
|
||||
"sortablePinnedApps",
|
||||
'ext_saved_settings_import_export',
|
||||
'sortablePinnedApps',
|
||||
null,
|
||||
true
|
||||
);
|
||||
@ -283,13 +283,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
}}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
@ -302,25 +302,25 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
{!isUsingImportExportSettings && (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "15px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: '15px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!myName ? (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
You need a registered Qortal name to save your pinned apps to
|
||||
@ -332,15 +332,15 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
{hasChanged && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
You have unsaved changes to your pinned apps. Save them to
|
||||
@ -349,13 +349,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
<Spacer height="10px" />
|
||||
<LoadingButton
|
||||
sx={{
|
||||
backgroundColor: "var(--green)",
|
||||
color: "black",
|
||||
backgroundColor: 'var(--green)',
|
||||
color: 'black',
|
||||
opacity: 0.7,
|
||||
fontWeight: "bold",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--green)",
|
||||
color: "black",
|
||||
fontWeight: 'bold',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--green)',
|
||||
color: 'black',
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
@ -372,7 +372,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Don't like your current local changes? Would you
|
||||
@ -385,13 +385,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
onClick={revertChanges}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
@ -405,7 +405,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Don't like your current local changes? Would you
|
||||
@ -428,15 +428,15 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
isUsingImportExportSettings !== true && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
The app was unable to download your existing QDN-saved
|
||||
@ -449,13 +449,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
onClick={saveToQdn}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
fontWeight: 'bold',
|
||||
opacity: 0.7,
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--danger)",
|
||||
color: "black",
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--danger)',
|
||||
color: 'black',
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
@ -467,15 +467,15 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
{!hasChanged && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
You currently do not have any changes to your pinned apps
|
||||
@ -488,19 +488,19 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
padding: "15px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: '15px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
justifyContent: "flex-end",
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
@ -517,8 +517,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
);
|
||||
if (Array.isArray(responseData)) {
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings_import_export",
|
||||
"sortablePinnedApps",
|
||||
'ext_saved_settings_import_export',
|
||||
'sortablePinnedApps',
|
||||
responseData,
|
||||
{
|
||||
isUsingImportExport: true,
|
||||
@ -529,7 +529,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
setIsUsingImportExportSettings(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
console.log('error', error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -544,14 +544,14 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
data64,
|
||||
});
|
||||
const blob = new Blob([encryptedData], {
|
||||
type: "text/plain",
|
||||
type: 'text/plain',
|
||||
});
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/:/g, "-"); // Safe timestamp for filenames
|
||||
const timestamp = new Date().toISOString().replace(/:/g, '-'); // Safe timestamp for filenames
|
||||
const filename = `qortal-new-ui-backup-settings-${timestamp}.txt`;
|
||||
await saveFileToDiskGeneric(blob, filename);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
console.log('error', error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -1,31 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
|
||||
import Alert from '@mui/material/Alert';
|
||||
|
||||
export const CustomizedSnackbars = ({open, setOpen, info, setInfo, duration}) => {
|
||||
|
||||
|
||||
|
||||
export const CustomizedSnackbars = ({
|
||||
open,
|
||||
setOpen,
|
||||
info,
|
||||
setInfo,
|
||||
duration,
|
||||
}) => {
|
||||
const handleClose = (
|
||||
event?: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason,
|
||||
reason?: SnackbarCloseReason
|
||||
) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setInfo(null)
|
||||
setInfo(null);
|
||||
};
|
||||
|
||||
if(!open) return null
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} autoHideDuration={info?.duration === null ? null : (duration || 6000)} onClose={handleClose}>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
open={open}
|
||||
autoHideDuration={info?.duration === null ? null : duration || 6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert
|
||||
|
||||
|
||||
onClose={handleClose}
|
||||
severity={info?.type}
|
||||
variant="filled"
|
||||
@ -36,4 +41,4 @@ export const CustomizedSnackbars = ({open, setOpen, info, setInfo, duration}) =
|
||||
</Snackbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -5,19 +5,21 @@ import {
|
||||
ListItemText,
|
||||
Collapse,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import PendingIcon from "@mui/icons-material/Pending";
|
||||
import TaskAltIcon from "@mui/icons-material/TaskAlt";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import PendingIcon from '@mui/icons-material/Pending';
|
||||
import TaskAltIcon from '@mui/icons-material/TaskAlt';
|
||||
import ExpandLess from '@mui/icons-material/ExpandLess';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { MyContext, getBaseApiReact, isMobile } from '../../App';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
|
||||
export const TaskManager = ({ getUserInfo }) => {
|
||||
const { txList, setTxList, memberGroups } = useContext(MyContext);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const intervals = useRef({});
|
||||
const theme = useTheme();
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen((prev) => !prev);
|
||||
@ -58,7 +60,9 @@ export const TaskManager = ({ getUserInfo }) => {
|
||||
}
|
||||
clearInterval(intervals.current[signature]);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
stop = false;
|
||||
}
|
||||
};
|
||||
@ -71,7 +75,7 @@ export const TaskManager = ({ getUserInfo }) => {
|
||||
let previousData = [...prev];
|
||||
memberGroups.forEach((group) => {
|
||||
const findGroup = txList.findIndex(
|
||||
(tx) => tx?.type === "joined-group" && tx?.groupId === group.groupId
|
||||
(tx) => tx?.type === 'joined-group' && tx?.groupId === group.groupId
|
||||
);
|
||||
if (findGroup !== -1 && !previousData[findGroup]?.done) {
|
||||
previousData[findGroup].done = true;
|
||||
@ -81,7 +85,7 @@ export const TaskManager = ({ getUserInfo }) => {
|
||||
memberGroups.forEach((group) => {
|
||||
const findGroup = txList.findIndex(
|
||||
(tx) =>
|
||||
tx?.type === "created-group" && tx?.groupName === group.groupName
|
||||
tx?.type === 'created-group' && tx?.groupName === group.groupName
|
||||
);
|
||||
if (findGroup !== -1 && !previousData[findGroup]?.done) {
|
||||
previousData[findGroup].done = true;
|
||||
@ -90,49 +94,52 @@ export const TaskManager = ({ getUserInfo }) => {
|
||||
|
||||
prev.forEach((tx, index) => {
|
||||
if (
|
||||
tx?.type === "leave-group" &&
|
||||
memberGroups.findIndex((group) => tx?.groupId === group.groupId) === -1
|
||||
tx?.type === 'leave-group' &&
|
||||
memberGroups.findIndex((group) => tx?.groupId === group.groupId) ===
|
||||
-1
|
||||
) {
|
||||
previousData[index].done = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
return previousData;
|
||||
});
|
||||
}, [memberGroups, getUserInfo]);
|
||||
|
||||
useEffect(()=> {
|
||||
|
||||
txList.forEach((tx) => {
|
||||
if (
|
||||
["created-common-secret", "joined-group-request", "join-request-accept"].includes(
|
||||
tx?.type
|
||||
) &&
|
||||
tx?.signature &&
|
||||
!tx.done
|
||||
) {
|
||||
if (!intervals.current[tx.signature]) {
|
||||
getStatus({ signature: tx.signature });
|
||||
}
|
||||
useEffect(() => {
|
||||
txList.forEach((tx) => {
|
||||
if (
|
||||
[
|
||||
'created-common-secret',
|
||||
'joined-group-request',
|
||||
'join-request-accept',
|
||||
].includes(tx?.type) &&
|
||||
tx?.signature &&
|
||||
!tx.done
|
||||
) {
|
||||
if (!intervals.current[tx.signature]) {
|
||||
getStatus({ signature: tx.signature });
|
||||
}
|
||||
if (tx?.type === "register-name" && tx?.signature && !tx.done) {
|
||||
if (!intervals.current[tx.signature]) {
|
||||
getStatus({ signature: tx.signature }, getUserInfo);
|
||||
}
|
||||
}
|
||||
if (tx?.type === 'register-name' && tx?.signature && !tx.done) {
|
||||
if (!intervals.current[tx.signature]) {
|
||||
getStatus({ signature: tx.signature }, getUserInfo);
|
||||
}
|
||||
if((tx?.type === "remove-rewardShare" || tx?.type === "add-rewardShare") && tx?.signature && !tx.done){
|
||||
if (!intervals.current[tx.signature]) {
|
||||
const sendEventForRewardShare = ()=> {
|
||||
executeEvent('refresh-rewardshare-list', {})
|
||||
}
|
||||
getStatus({ signature: tx.signature }, sendEventForRewardShare);
|
||||
}
|
||||
}
|
||||
if (
|
||||
(tx?.type === 'remove-rewardShare' || tx?.type === 'add-rewardShare') &&
|
||||
tx?.signature &&
|
||||
!tx.done
|
||||
) {
|
||||
if (!intervals.current[tx.signature]) {
|
||||
const sendEventForRewardShare = () => {
|
||||
executeEvent('refresh-rewardshare-list', {});
|
||||
};
|
||||
getStatus({ signature: tx.signature }, sendEventForRewardShare);
|
||||
}
|
||||
});
|
||||
|
||||
}, [txList])
|
||||
}
|
||||
});
|
||||
}, [txList]);
|
||||
|
||||
if (isMobile || txList?.length === 0 || txList.every((item) => item?.done))
|
||||
return null;
|
||||
@ -143,43 +150,48 @@ export const TaskManager = ({ getUserInfo }) => {
|
||||
<IconButton
|
||||
onClick={handleClick}
|
||||
sx={{
|
||||
// position: "fixed",
|
||||
// bottom: 16,
|
||||
// right: 16,
|
||||
bgcolor: "primary.main",
|
||||
color: "white",
|
||||
":hover": { bgcolor: "primary.dark" },
|
||||
bgcolor: theme.palette.primary.main,
|
||||
color: theme.palette.text.primary,
|
||||
':hover': { bgcolor: theme.palette.primary },
|
||||
}}
|
||||
>
|
||||
{txList.some((item) => !item.done) ? <PendingIcon /> : <TaskAltIcon />}
|
||||
{txList.some((item) => !item.done) ? (
|
||||
<PendingIcon />
|
||||
) : (
|
||||
<TaskAltIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
)}
|
||||
{open && (
|
||||
<List
|
||||
sx={{
|
||||
position: "fixed",
|
||||
bgcolor: theme.palette.background.paper,
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
width: "300px",
|
||||
maxHeight: "400px",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 4,
|
||||
overflow: "auto",
|
||||
maxHeight: '400px',
|
||||
overflow: 'auto',
|
||||
padding: '0px',
|
||||
position: 'fixed',
|
||||
right: 16,
|
||||
width: '300px',
|
||||
zIndex: 10,
|
||||
padding: '0px'
|
||||
}}
|
||||
component="nav"
|
||||
>
|
||||
<ListItemButton onClick={handleClick}>
|
||||
<ListItemButton onClick={handleClick}>
|
||||
<ListItemIcon>
|
||||
{txList.some((item) => !item.done) ? (
|
||||
<PendingIcon sx={{
|
||||
color:'white'
|
||||
}} />
|
||||
<PendingIcon
|
||||
sx={{
|
||||
color: theme.palette.primary,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TaskAltIcon sx={{
|
||||
color:'white'
|
||||
}} />
|
||||
<TaskAltIcon
|
||||
sx={{
|
||||
color: theme.palette.primary,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Ongoing Transactions" />
|
@ -1,6 +1,7 @@
|
||||
import { createContext, useContext, useState, useMemo } from 'react';
|
||||
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
|
||||
import { darkTheme, lightTheme } from '../../styles/theme';
|
||||
import { darkTheme } from '../../styles/theme-dark';
|
||||
import { lightTheme } from '../../styles/theme-light';
|
||||
|
||||
const ThemeContext = createContext({
|
||||
themeMode: 'light',
|
||||
|