Merge pull request #16 from nbenaglia/feature/refactor-css-themes

Refactor css themes in main pages
This commit is contained in:
Phillip 2025-04-13 17:16:37 +03:00 committed by GitHub
commit 9bf00a2713
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 15625 additions and 13320 deletions

23
.prettierrc Normal file
View File

@ -0,0 +1,23 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"experimentalTernaries": false,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleAttributePerLine": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}

File diff suppressed because it is too large Load Diff

View File

@ -1,258 +0,0 @@
import { Typography, Box, TextField, InputLabel } from "@mui/material";
import { styled } from "@mui/system";
export const AppContainer = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: "column",
width: "100vw",
background: "rgba(39, 40, 44, 1)",
height: "100vh",
radius: "15px",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
overflow: 'hidden'
}));
export const AuthenticatedContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
height: "100%",
justifyContent: "space-between",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerLeft = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: "column",
height: "100%",
width: "100%",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerRight = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: "column",
width: "60px",
height: "100%",
background: "rgba(0, 0, 0, 0.1)",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerTop = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
width: "100%px",
height: "60px",
background: "rgba(0, 0, 0, 0.1)",
padding: "20px",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextP = styled(Typography)(({ theme }) => ({
fontSize: "13px",
fontWeight: 600,
fontFamily: "Inter",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextItalic = styled("span")(({ theme }) => ({
fontSize: "13px",
fontWeight: 600,
fontFamily: "Inter",
fontStyle: "italic",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextSpan = styled("span")(({ theme }) => ({
fontSize: "13px",
fontFamily: "Inter",
fontWeight: 800,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AddressBox = styled(Box)(({ theme }) => ({
display: "flex",
border: `1px solid ${
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.5)"
: "rgba(0, 0, 0, 0.3)"
}`,
justifyContent: "space-between",
alignItems: "center",
width: "auto",
height: "25px",
padding: "5px 15px",
gap: "5px",
borderRadius: "100px",
fontFamily: "Inter",
fontSize: "12px",
fontWeight: 600,
lineHeight: "14.52px",
textAlign: "left",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
cursor: "pointer",
transition: "all 0.2s",
"&:hover": {
backgroundColor:
theme.palette.mode === "dark"
? "rgba(41, 41, 43, 1)"
: "rgba(240, 240, 240, 1)",
color: theme.palette.mode === "dark" ? "#fff" : "#000",
"svg path": {
fill: theme.palette.mode === "dark" ? "#fff" : "#000",
},
},
}));
export const CustomButton = styled(Box)(({ theme }) => ({
boxSizing: "border-box",
padding: "15px 20px",
gap: "10px",
border: `0.5px solid ${
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.5)"
: "rgba(0, 0, 0, 0.3)"
}`,
filter: "drop-shadow(1px 4px 10.5px rgba(0, 0, 0, 0.3))",
borderRadius: "5px",
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
width: "fit-content",
minWidth: "160px",
cursor: "pointer",
transition: "all 0.2s",
fontWeight: 600,
fontFamily: "Inter",
textAlign: "center",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
"&:hover": {
backgroundColor:
theme.palette.mode === "dark"
? "rgba(41, 41, 43, 1)"
: "rgba(230, 230, 230, 1)",
color: "#fff",
"svg path": {
fill: "#fff",
},
},
}));
interface CustomButtonProps {
bgColor?: string;
color?: string;
}
export const CustomButtonAccept = styled(Box)<CustomButtonProps>(
({ bgColor, color, theme }) => ({
boxSizing: "border-box",
padding: "15px 20px",
gap: "10px",
border: `0.5px solid ${
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.5)"
: "rgba(0, 0, 0, 0.3)"
}`,
filter: "drop-shadow(1px 4px 10.5px rgba(0,0,0,0.3))",
borderRadius: 5,
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
width: "fit-content",
transition: "all 0.2s",
minWidth: 160,
cursor: "pointer",
fontWeight: 600,
fontFamily: "Inter",
textAlign: "center",
opacity: 0.7,
// Color and backgroundColor with fallbacks
backgroundColor: bgColor || (theme.palette.mode === "dark" ? "#1d1d1d" : "#f5f5f5"),
color: color || (theme.palette.mode === "dark" ? "#fff" : "#000"),
"&:hover": {
opacity: 1,
backgroundColor: bgColor || (theme.palette.mode === "dark" ? "rgba(41, 41, 43, 1)" : "rgba(230, 230, 230, 1)"),
color: color || "#fff",
svg: {
path: {
fill: color || "#fff",
},
},
},
})
);
export const CustomInput = styled(TextField)(({ theme }) => ({
width: "183px", // Adjust the width as needed
borderRadius: "5px",
// backgroundColor: "rgba(30, 30, 32, 1)",
outline: "none",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
input: {
fontSize: 10,
fontFamily: "Inter",
fontWeight: 400,
color: "white",
"&::placeholder": {
fontSize: 16,
color: "rgba(255, 255, 255, 0.2)",
},
outline: "none",
padding: "10px",
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
border: "0.5px solid rgba(255, 255, 255, 0.5)",
},
"&:hover fieldset": {
border: "0.5px solid rgba(255, 255, 255, 0.5)",
},
"&.Mui-focused fieldset": {
border: "0.5px solid rgba(255, 255, 255, 0.5)",
},
},
"& .MuiInput-underline:before": {
borderBottom: "none",
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before": {
borderBottom: "none",
},
"& .MuiInput-underline:after": {
borderBottom: "none",
},
}));
export const CustomLabel = styled(InputLabel)(({ theme }) => ({
fontWeight: 400,
fontFamily: "Inter",
fontSize: "10px",
lineHeight: "12px",
color:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.5)"
: "rgba(0, 0, 0, 0.5)",
}));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,58 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Divider from "@mui/material/Divider";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Avatar from "@mui/material/Avatar";
import Typography from "@mui/material/Typography";
import { Box, Button, ButtonBase, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Input } from "@mui/material";
import { CustomButton } from "./App-styles";
import { useDropzone } from "react-dropzone";
import EditIcon from "@mui/icons-material/Edit";
import { Label } from "./components/Group/AddGroup";
import { Spacer } from "./common/Spacer";
import { getWallets, storeWallets, walletVersion } from "./background";
import { useModal } from "./common/useModal";
import PhraseWallet from "./utils/generateWallet/phrase-wallet";
import { decryptStoredWalletFromSeedPhrase } from "./utils/decryptWallet";
import { crypto } from "./constants/decryptWallet";
import { LoadingButton } from "@mui/lab";
import { PasswordField } from "./components";
import { HtmlTooltip } from "./ExtStates/NotAuthenticated";
import { GlobalContext } from "./App";
import React, { useContext, useEffect, useState } from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Divider from '@mui/material/Divider';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';
import Typography from '@mui/material/Typography';
import {
Box,
Button,
ButtonBase,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
Input,
} from '@mui/material';
import { CustomButton } from './styles/App-styles';
import { useDropzone } from 'react-dropzone';
import EditIcon from '@mui/icons-material/Edit';
import { Label } from './components/Group/AddGroup';
import { Spacer } from './common/Spacer';
import { getWallets, storeWallets, walletVersion } from './background';
import { useModal } from './common/useModal';
import PhraseWallet from './utils/generateWallet/phrase-wallet';
import { decryptStoredWalletFromSeedPhrase } from './utils/decryptWallet';
import { crypto } from './constants/decryptWallet';
import { LoadingButton } from '@mui/lab';
import { PasswordField } from './components';
import { HtmlTooltip } from './ExtStates/NotAuthenticated';
import { GlobalContext } from './App';
const parsefilenameQortal = (filename)=> {
return filename.startsWith("qortal_backup_") ? filename.slice(14) : filename;
}
const parsefilenameQortal = (filename) => {
return filename.startsWith('qortal_backup_') ? filename.slice(14) : filename;
};
export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
const [wallets, setWallets] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [seedValue, setSeedValue] = useState("");
const [seedName, setSeedName] = useState("");
const [seedError, setSeedError] = useState("");
const [seedValue, setSeedValue] = useState('');
const [seedName, setSeedName] = useState('');
const [seedError, setSeedError] = useState('');
const { hasSeenGettingStarted } = useContext(GlobalContext);
const [password, setPassword] = useState("");
const [password, setPassword] = useState('');
const [isOpenSeedModal, setIsOpenSeedModal] = useState(false);
const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false);
const { isShow, onCancel, onOk, show, } = useModal();
const { isShow, onCancel, onOk, show } = useModal();
const { getRootProps, getInputProps } = useDropzone({
accept: {
"application/json": [".json"], // Only accept JSON files
'application/json': ['.json'], // Only accept JSON files
},
onDrop: async (acceptedFiles) => {
const files: any = acceptedFiles;
@ -53,8 +63,8 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
const fileContents = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onabort = () => reject("File reading was aborted");
reader.onerror = () => reject("File reading has failed");
reader.onabort = () => reject('File reading was aborted');
reader.onerror = () => reject('File reading has failed');
reader.onload = () => {
// Resolve the promise with the reader result when reading completes
resolve(reader.result);
@ -63,9 +73,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
// Read the file as text
reader.readAsText(file);
});
if (typeof fileContents !== "string") continue;
const parsedData = JSON.parse(fileContents)
importedWallets.push({...parsedData, filename: file?.name});
if (typeof fileContents !== 'string') continue;
const parsedData = JSON.parse(fileContents);
importedWallets.push({ ...parsedData, filename: file?.name });
} catch (error) {
console.error(error);
}
@ -108,78 +118,80 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
});
};
const handleSetSeedValue = async ()=> {
const handleSetSeedValue = async () => {
try {
setIsOpenSeedModal(true)
const {seedValue, seedName, password} = await show({
message: "",
publishFee: "",
setIsOpenSeedModal(true);
const { seedValue, seedName, password } = await show({
message: '',
publishFee: '',
});
setIsLoadingEncryptSeed(true)
const res = await decryptStoredWalletFromSeedPhrase(seedValue)
setIsLoadingEncryptSeed(true);
const res = await decryptStoredWalletFromSeedPhrase(seedValue);
const wallet2 = new PhraseWallet(res, walletVersion);
const wallet = await wallet2.generateSaveWalletData(
password,
crypto.kdfThreads,
() => {}
);
if(wallet?.address0){
setWallets([...wallets, {
if (wallet?.address0) {
setWallets([
...wallets,
{
...wallet,
name: seedName
}]);
setIsOpenSeedModal(false)
setSeedValue('')
setSeedName('')
setPassword('')
setSeedError('')
name: seedName,
},
]);
setIsOpenSeedModal(false);
setSeedValue('');
setSeedName('');
setPassword('');
setSeedError('');
} else {
setSeedError('Could not create account.')
setSeedError('Could not create account.');
}
} catch (error) {
setSeedError(error?.message || 'Could not create account.')
setSeedError(error?.message || 'Could not create account.');
} finally {
setIsLoadingEncryptSeed(false)
}
setIsLoadingEncryptSeed(false);
}
};
const selectedWalletFunc = (wallet) => {
setRawWallet(wallet);
setExtState("wallet-dropped");
setExtState('wallet-dropped');
};
useEffect(()=> {
setIsLoading(true)
getWallets().then((res)=> {
if(res && Array.isArray(res)){
setWallets(res)
useEffect(() => {
setIsLoading(true);
getWallets()
.then((res) => {
if (res && Array.isArray(res)) {
setWallets(res);
}
setIsLoading(false)
}).catch((error)=> {
console.error(error)
setIsLoading(false)
setIsLoading(false);
})
}, [])
.catch((error) => {
console.error(error);
setIsLoading(false);
});
}, []);
useEffect(()=> {
if(!isLoading && wallets && Array.isArray(wallets)){
storeWallets(wallets)
useEffect(() => {
if (!isLoading && wallets && Array.isArray(wallets)) {
storeWallets(wallets);
}
}, [wallets, isLoading])
}, [wallets, isLoading]);
if(isLoading) return null
if (isLoading) return null;
return (
<div>
{(wallets?.length === 0 ||
!wallets) ? (
{wallets?.length === 0 || !wallets ? (
<>
<Typography>No accounts saved</Typography>
<Spacer height="75px" />
</>
): (
) : (
<>
<Typography>Your saved accounts</Typography>
<Spacer height="30px" />
@ -198,13 +210,12 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
{wallets?.length > 0 && (
<List
sx={{
width: "100%",
maxWidth: "500px",
maxHeight: "60vh",
overflowY: "auto",
overflowX: "hidden",
backgroundColor: "rgb(30 30 32 / 70%)",
width: '100%',
maxWidth: '500px',
maxHeight: '60vh',
overflowY: 'auto',
overflowX: 'hidden',
backgroundColor: 'rgb(30 30 32 / 70%)',
}}
>
{wallets?.map((wallet, idx) => {
@ -226,46 +237,64 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
display: 'flex',
gap: '10px',
alignItems: 'center',
position: wallets?.length === 0 ? 'relative' : 'fixed',
bottom: wallets?.length === 0 ? 'unset' : '20px',
right: wallets?.length === 0 ? 'unset' : '20px'
right: wallets?.length === 0 ? 'unset' : '20px',
}}
>
<HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true}
title={
<React.Fragment>
<Typography color="inherit" sx={{
fontSize: '16px'
}}>Already have a Qortal account? Enter your secret backup phrase here to access it. This phrase is one of the ways to recover your account.</Typography>
<Typography
color="inherit"
sx={{
fontSize: '16px',
}}
>
Already have a Qortal account? Enter your secret backup phrase
here to access it. This phrase is one of the ways to recover
your account.
</Typography>
</React.Fragment>
}
>
<CustomButton onClick={handleSetSeedValue} sx={{
padding: '10px'
}} >
<CustomButton
onClick={handleSetSeedValue}
sx={{
padding: '10px',
}}
>
Add seed-phrase
</CustomButton>
</HtmlTooltip>
<HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true}
title={
<React.Fragment>
<Typography color="inherit" sx={{
fontSize: '16px'
}}>Use this option to connect additional Qortal wallets you've already made, in order to login with them afterwards. You will need access to your backup JSON file in order to do so.</Typography>
<Typography
color="inherit"
sx={{
fontSize: '16px',
}}
>
Use this option to connect additional Qortal wallets you've
already made, in order to login with them afterwards. You will
need access to your backup JSON file in order to do so.
</Typography>
</React.Fragment>
}
>
<CustomButton sx={{
padding: '10px'
}} {...getRootProps()}>
<CustomButton
sx={{
padding: '10px',
}}
{...getRootProps()}
>
<input {...getInputProps()} />
Add account
</CustomButton>
@ -278,19 +307,19 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
aria-describedby="alert-dialog-description"
onKeyDown={(e) => {
if (e.key === 'Enter' && seedValue && seedName && password) {
onOk({seedValue, seedName, password});
onOk({ seedValue, seedName, password });
}
}}
>
<DialogTitle id="alert-dialog-title">
Type or paste in your seed-phrase
</DialogTitle>
<DialogContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<Label>Name</Label>
@ -299,7 +328,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
value={seedName}
onChange={(e) => setSeedName(e.target.value)}
/>
<Spacer height="7px" />
<Label>Seed-phrase</Label>
<PasswordField
placeholder="Seed-phrase"
@ -308,9 +339,10 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
onChange={(e) => setSeedValue(e.target.value)}
autoComplete="off"
sx={{
width: '100%'
width: '100%',
}}
/>
<Spacer height="7px" />
<Label>Choose new password</Label>
@ -320,21 +352,23 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
onChange={(e) => setPassword(e.target.value)}
autoComplete="off"
sx={{
width: '100%'
width: '100%',
}}
/>
</Box>
</DialogContent>
<DialogActions>
<Button disabled={isLoadingEncryptSeed} variant="contained" onClick={()=> {
setIsOpenSeedModal(false)
setSeedValue('')
setSeedName('')
setPassword('')
setSeedError('')
}}>
<Button
disabled={isLoadingEncryptSeed}
variant="contained"
onClick={() => {
setIsOpenSeedModal(false);
setSeedValue('');
setSeedName('');
setPassword('');
setSeedError('');
}}
>
Close
</Button>
<LoadingButton
@ -342,28 +376,30 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
disabled={!seedValue || !seedName || !password}
variant="contained"
onClick={() => {
if(!seedValue || !seedName || !password) return
onOk({seedValue, seedName, password});
if (!seedValue || !seedName || !password) return;
onOk({ seedValue, seedName, password });
}}
autoFocus
>
Add
</LoadingButton>
<Typography sx={{
<Typography
sx={{
fontSize: '14px',
visibility: seedError ? 'visible' : 'hidden'
}}>{seedError}</Typography>
visibility: seedError ? 'visible' : 'hidden',
}}
>
{seedError}
</Typography>
</DialogActions>
</Dialog>
</div>
);
};
const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
const [name, setName] = useState("");
const [note, setNote] = useState("");
const [name, setName] = useState('');
const [note, setNote] = useState('');
const [isEdit, setIsEdit] = useState(false);
useEffect(() => {
@ -382,52 +418,62 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
}}
sx={{
width: '100%',
padding: '10px'
padding: '10px',
}}
>
<ListItem
sx={{
bgcolor: "background.paper",
bgcolor: 'background.paper', // TODO: set background color
flexGrow: 1,
"&:hover": { backgroundColor: "secondary.main", transform: "scale(1.01)" },
transition: "all 0.1s ease-in-out",
'&:hover': {
backgroundColor: 'secondary.main',
transform: 'scale(1.01)',
},
transition: 'all 0.1s ease-in-out',
}}
alignItems="flex-start"
>
<ListItemAvatar>
<Avatar alt="" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary={wallet?.name ? wallet.name : wallet?.filename ? parsefilenameQortal(wallet?.filename) : "No name"}
primary={
wallet?.name
? wallet.name
: wallet?.filename
? parsefilenameQortal(wallet?.filename)
: 'No name'
}
secondary={
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<Typography
component="span"
variant="body2"
sx={{ color: "text.primary", display: "inline" }}
sx={{ color: 'text.primary', display: 'inline' }}
>
{wallet?.address0}
</Typography>
{wallet?.note}
<Typography sx={{
<Typography
sx={{
textAlign: 'end',
marginTop: '5px'
}}>Login</Typography>
marginTop: '5px',
}}
>
Login
</Typography>
</Box>
}
/>
</ListItem>
<IconButton
sx={{
alignSelf: 'flex-start'
alignSelf: 'flex-start',
}}
onClick={(e) => {
e.stopPropagation();
@ -438,7 +484,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
>
<EditIcon
sx={{
color: "white",
color: 'white',
}}
/>
</IconButton>
@ -446,7 +492,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
{isEdit && (
<Box
sx={{
padding: "8px",
padding: '8px',
}}
>
<Label>Name</Label>
@ -455,10 +501,12 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
value={name}
onChange={(e) => setName(e.target.value)}
sx={{
width: "100%",
width: '100%',
}}
/>
<Spacer height="10px" />
<Label>Note</Label>
<Input
placeholder="Note"
@ -468,29 +516,35 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
maxLength: 100,
}}
sx={{
width: "100%",
width: '100%',
}}
/>
<Spacer height="10px" />
<Box
sx={{
display: "flex",
gap: "20px",
justifyContent: "flex-end",
width: "100%",
display: 'flex',
gap: '20px',
justifyContent: 'flex-end',
width: '100%',
}}
>
<Button size="small" variant="contained" onClick={() => setIsEdit(false)}>
<Button
size="small"
variant="contained"
onClick={() => setIsEdit(false)}
>
Close
</Button>
<Button
sx={{
backgroundColor: 'var(--danger)',
"&:hover": {
backgroundColor: "var(--danger)",
'&:hover': {
backgroundColor: 'var(--danger)',
},
"&:focus": {
backgroundColor: "var(--danger)",
'&:focus': {
backgroundColor: 'var(--danger)',
},
}}
size="small"
@ -501,12 +555,12 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
</Button>
<Button
sx={{
backgroundColor: "#5EB049",
"&:hover": {
backgroundColor: "#5EB049",
backgroundColor: '#5EB049',
'&:hover': {
backgroundColor: '#5EB049',
},
"&:focus": {
backgroundColor: "#5EB049",
'&:focus': {
backgroundColor: '#5EB049',
},
}}
size="small"
@ -525,9 +579,6 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
</Box>
</Box>
)}
</>
);
};

View File

@ -1,8 +1,18 @@
import React from "react";
import { useTheme } from '@mui/material';
import { SVGProps } from '../svgs/interfaces';
export const WalletIcon: React.FC<SVGProps> = ({
color,
width,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
export const WalletIcon = ({ color, height, width }) => {
return (
<svg
{...children}
width={width || 30}
height={width || 30}
viewBox="0 0 31 31"
@ -11,13 +21,13 @@ export const WalletIcon = ({ color, height, width }) => {
>
<path
d="M19.0118 22.0891C18.0124 22.8671 16.6997 23.3391 15.2618 23.3391C13.8241 23.3391 12.5113 22.8671 11.5118 22.0891"
stroke={color}
stroke={setColor}
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M3.20108 17.356C2.7598 14.4844 2.53917 13.0486 3.08205 11.7758C3.62493 10.503 4.82938 9.63215 7.23827 7.89044L9.03808 6.58911C12.0347 4.42245 13.5331 3.33911 15.2618 3.33911C16.9907 3.33911 18.4889 4.42245 21.4856 6.58911L23.2854 7.89044C25.6943 9.63215 26.8988 10.503 27.4417 11.7758C27.9846 13.0486 27.7639 14.4844 27.3226 17.356L26.9463 19.8046C26.3208 23.8752 26.0079 25.9106 24.5481 27.1249C23.0882 28.3391 20.9539 28.3391 16.6853 28.3391H13.8383C9.56977 28.3391 7.43548 28.3391 5.97559 27.1249C4.5157 25.9106 4.20293 23.8752 3.57738 19.8046L3.20108 17.356Z"
stroke={color}
stroke={setColor}
strokeWidth="2"
strokeLinejoin="round"
/>

View File

@ -1,11 +1,11 @@
import React from "react";
import { styled } from "@mui/system";
import { SVGProps } from "./interfaces";
import React from 'react';
import { styled } from '@mui/system';
import { SVGProps } from './interfaces';
// Create a styled container with hover effects
const SvgContainer = styled("svg")({
"& path": {
fill: "rgba(41, 41, 43, 1)", // Default to red if no color prop
const SvgContainer = styled('svg')({
'& path': {
fill: 'rgba(41, 41, 43, 1)', // Default to red if no color prop
},
});
@ -20,7 +20,7 @@ export const CreateThreadIcon: React.FC<SVGProps> = ({ color, opacity }) => {
>
<path
d="M0 9.80209V9.0205C0.0460138 8.67679 0.080024 8.31425 0.144043 7.98466C0.469856 6.30568 1.25577 4.79934 2.38071 3.6977C4.13924 1.88262 6.22987 0.985679 8.52256 0.674927C9.9086 0.485649 11.3116 0.565177 12.6758 0.910345C14.5124 1.34351 16.1889 2.2075 17.6053 3.67886C18.7276 4.84183 19.5319 6.24257 19.858 7.98466C19.918 8.31189 19.952 8.64383 20 8.97577V9.80209C19.9827 9.8676 19.9693 9.93447 19.96 10.0022C19.8708 11.2186 19.5113 12.3861 18.9177 13.3875C17.961 15.0025 16.6297 16.2594 15.0825 17.0082C12.4657 18.3525 9.75693 18.5667 6.98209 17.8346C6.8589 17.8074 6.73157 17.8264 6.61799 17.8887C5.15955 18.7339 3.70511 19.5908 2.24867 20.4501C2.18866 20.4854 2.12464 20.5183 2.0146 20.5748L3.78714 16.3703C3.37301 16.0148 2.96889 15.7017 2.60078 15.3415C1.42243 14.1879 0.556167 12.7895 0.182055 11.0192C0.0980294 10.6213 0.060018 10.2094 0 9.80209ZM14.0042 10.5931C14.1362 10.5968 14.2676 10.5698 14.3907 10.5135C14.5138 10.4572 14.6262 10.3728 14.7214 10.2651C14.8167 10.1574 14.8928 10.0286 14.9455 9.8861C14.9982 9.7436 15.0264 9.59023 15.0285 9.43484V9.4113C15.0285 9.25517 15.0024 9.10058 14.9516 8.95634C14.9008 8.8121 14.8264 8.68104 14.7326 8.57064C14.6388 8.46025 14.5274 8.37268 14.4048 8.31293C14.2823 8.25319 14.1509 8.22243 14.0182 8.22243C13.8855 8.22243 13.7542 8.25319 13.6316 8.31293C13.509 8.37268 13.3976 8.46025 13.3038 8.57064C13.21 8.68104 13.1356 8.8121 13.0848 8.95634C13.034 9.10058 13.0079 9.25517 13.0079 9.4113C13.0074 9.56588 13.0327 9.71906 13.0825 9.86211C13.1323 10.0052 13.2055 10.1353 13.2981 10.245C13.3906 10.3547 13.5005 10.442 13.6217 10.5017C13.7429 10.5614 13.8728 10.5925 14.0042 10.5931ZM10.003 10.5931C10.203 10.5926 10.3983 10.5225 10.5644 10.3915C10.7306 10.2606 10.86 10.0746 10.9364 9.85719C11.0129 9.63976 11.0329 9.40056 10.9939 9.16977C10.9549 8.93898 10.8588 8.72694 10.7175 8.5604C10.5763 8.39385 10.3962 8.28026 10.2002 8.23396C10.0041 8.18765 9.80084 8.21071 9.61591 8.30022C9.43099 8.38973 9.27273 8.54168 9.1611 8.7369C9.04948 8.93212 8.98949 9.16187 8.9887 9.39717C8.98975 9.71356 9.09688 10.0167 9.28682 10.2406C9.47675 10.4646 9.73413 10.5912 10.003 10.5931ZM4.98349 9.3854C4.9836 9.61979 5.04316 9.8488 5.15456 10.0431C5.26595 10.2374 5.42411 10.3882 5.60876 10.476C5.79341 10.5639 5.99616 10.5849 6.19102 10.5364C6.38588 10.4878 6.56399 10.3719 6.70252 10.2035C6.84105 10.0351 6.93371 9.82183 6.96861 9.59108C7.00352 9.36032 6.97909 9.12255 6.89845 8.90823C6.8178 8.69392 6.68463 8.51281 6.51597 8.38811C6.34732 8.26342 6.15087 8.20081 5.95179 8.20831C5.69208 8.21809 5.44579 8.34641 5.26507 8.56611C5.08434 8.78581 4.98336 9.07963 4.98349 9.3854Z"
fill="#29292B"
fill={color}
/>
</SvgContainer>
);

View File

@ -0,0 +1,32 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Download: 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="20"
height="20"
viewBox="0 0 20 20"
fill={setColor}
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={setColor}
fill-opacity={setOpacity}
/>
</svg>
);
};

View File

@ -0,0 +1,28 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Logout: 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="18"
height="20"
viewBox="0 0 18 20"
fill={setColor}
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={setColor}
fill-opacity={setOpacity}
/>
</svg>
);
};

View File

@ -0,0 +1,27 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Return: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
{...children}
width="20"
height="16"
viewBox="0 0 20 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="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}
/>
</svg>
);
};

View File

@ -1,12 +1,14 @@
import { useTheme } from "@mui/material";
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const SaveIcon = ({ color }) => {
export const SaveIcon: React.FC<SVGProps> = ({ color, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
{...children}
width="24"
height="24"
viewBox="0 0 24 24"

View File

@ -6,14 +6,23 @@ import { SVGProps } from './interfaces';
const SvgContainer = styled('svg')({
'& path': {
fill: 'rgba(41, 41, 43, 1)', // Default to red if no color prop
}
},
});
export const SendNewMessage:React.FC<SVGProps> = ({ color, opacity }) => {
export const SendNewMessage: React.FC<SVGProps> = ({ color, opacity }) => {
return (
<SvgContainer width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M3.33271 10.2306C2.88006 10.001 2.89088 9.65814 3.3554 9.46527L16.3563 4.06742C16.8214 3.87427 17.0961 4.11004 16.9689 4.59692L14.1253 15.4847C13.9985 15.9703 13.5515 16.1438 13.1241 15.8705L10.0773 13.9219C9.8629 13.7848 9.56272 13.8345 9.40985 14.0292L8.41215 15.2997C8.10197 15.6946 7.71724 15.6311 7.5525 15.1567L6.67584 12.6326C6.51125 12.1587 6.01424 11.5902 5.55821 11.359L3.33271 10.2306Z" />
<SvgContainer
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.33271 10.2306C2.88006 10.001 2.89088 9.65814 3.3554 9.46527L16.3563 4.06742C16.8214 3.87427 17.0961 4.11004 16.9689 4.59692L14.1253 15.4847C13.9985 15.9703 13.5515 16.1438 13.1241 15.8705L10.0773 13.9219C9.8629 13.7848 9.56272 13.8345 9.40985 14.0292L8.41215 15.2997C8.10197 15.6946 7.71724 15.6311 7.5525 15.1567L6.67584 12.6326C6.51125 12.1587 6.01424 11.5902 5.55821 11.359L3.33271 10.2306Z"
/>
</SvgContainer>
);
};

View File

@ -1,16 +1,16 @@
import React from 'react';
export const StarEmptyIcon = () => {
return (
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.2726 0.162533L7.89126 3.31595C7.9357 3.40243 8.02078 3.46234 8.11994 3.47588L11.7399 3.98173C11.8542 3.99736 11.9496 4.07446 11.9853 4.18022C12.0206 4.28598 11.9913 4.40215 11.9084 4.47977L9.28882 6.93449V6.93397C9.21729 7.00117 9.18478 7.09807 9.20157 7.19288L9.81988 10.6588C9.83939 10.7682 9.79278 10.8786 9.69903 10.9443C9.60529 11.0094 9.48119 11.0182 9.37931 10.9667L6.14144 9.32987C6.05311 9.28559 5.9469 9.28559 5.85856 9.32987L2.62069 10.9667C2.51881 11.0182 2.39472 11.0094 2.30096 10.9443C2.20722 10.8786 2.16062 10.7682 2.18012 10.6588L2.79842 7.19288C2.81522 7.09807 2.78271 7.00117 2.71118 6.93397L0.0916083 4.47978C0.0086971 4.40216 -0.0205644 4.28599 0.0146582 4.18023C0.0504232 4.07448 0.145798 3.99738 0.260135 3.98175L3.88006 3.47589C3.97923 3.46235 4.0643 3.40244 4.10874 3.31596L5.7274 0.162545C5.77888 0.0630431 5.88455 0 5.99997 0C6.11539 0 6.22113 0.0630238 6.2726 0.162533Z" fill="#727376"/>
</svg>
<svg
width="12"
height="11"
viewBox="0 0 12 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.2726 0.162533L7.89126 3.31595C7.9357 3.40243 8.02078 3.46234 8.11994 3.47588L11.7399 3.98173C11.8542 3.99736 11.9496 4.07446 11.9853 4.18022C12.0206 4.28598 11.9913 4.40215 11.9084 4.47977L9.28882 6.93449V6.93397C9.21729 7.00117 9.18478 7.09807 9.20157 7.19288L9.81988 10.6588C9.83939 10.7682 9.79278 10.8786 9.69903 10.9443C9.60529 11.0094 9.48119 11.0182 9.37931 10.9667L6.14144 9.32987C6.05311 9.28559 5.9469 9.28559 5.85856 9.32987L2.62069 10.9667C2.51881 11.0182 2.39472 11.0094 2.30096 10.9443C2.20722 10.8786 2.16062 10.7682 2.18012 10.6588L2.79842 7.19288C2.81522 7.09807 2.78271 7.00117 2.71118 6.93397L0.0916083 4.47978C0.0086971 4.40216 -0.0205644 4.28599 0.0146582 4.18023C0.0504232 4.07448 0.145798 3.99738 0.260135 3.98175L3.88006 3.47589C3.97923 3.46235 4.0643 3.40244 4.10874 3.31596L5.7274 0.162545C5.77888 0.0630431 5.88455 0 5.99997 0C6.11539 0 6.22113 0.0630238 6.2726 0.162533Z"
fill="#727376"
/>
</svg>
);
};

View File

@ -1,5 +1,3 @@
import React from "react";
export const StarFilledIcon = () => {
return (
<svg

View File

@ -1,6 +1,8 @@
export interface SVGProps {
color: string
height: string
width: string
opacity?: number
import React from 'react';
export interface SVGProps extends React.SVGProps<SVGSVGElement> {
color?: string;
height?: string;
opacity?: number;
width?: string;
}

View File

@ -1,53 +1,57 @@
import { atom, selectorFamily } from 'recoil';
export const sortablePinnedAppsAtom = atom({
key: 'sortablePinnedAppsFromAtom',
default: [{
default: [
{
name: 'Q-Tube',
service: 'APP'
}, {
service: 'APP',
},
{
name: 'Q-Mail',
service: 'APP'
}, {
service: 'APP',
},
{
name: 'Q-Share',
service: 'APP'
}, {
service: 'APP',
},
{
name: 'Q-Fund',
service: 'APP'
}, {
service: 'APP',
},
{
name: 'Q-Shop',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Trade',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Support',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Manager',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Blog',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Mintership',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Wallets',
service: 'APP'
service: 'APP',
},
{
name: 'Q-Search',
service: 'APP'
service: 'APP',
},
],
],
});
export const canSaveSettingToQdnAtom = atom({
@ -69,12 +73,12 @@ export const oldPinnedAppsAtom = atom({
key: 'oldPinnedAppsAtom',
default: [],
});
export const isUsingImportExportSettingsAtom = atom({
key: 'isUsingImportExportSettingsAtom',
default: null,
});
export const fullScreenAtom = atom({
key: 'fullScreenAtom',
default: false,
@ -117,7 +121,9 @@ export const resourceDownloadControllerAtom = atom({
export const resourceKeySelector = selectorFamily({
key: 'resourceKeySelector',
get: (key) => ({ get }) => {
get:
(key) =>
({ get }) => {
const resources = get(resourceDownloadControllerAtom);
return resources[key] || null; // Return the value for the key or null if not found
},
@ -130,7 +136,9 @@ export const blobControllerAtom = atom({
export const blobKeySelector = selectorFamily({
key: 'blobKeySelector',
get: (key) => ({ get }) => {
get:
(key) =>
({ get }) => {
const blobs = get(blobControllerAtom);
return blobs[key] || null; // Return the value for the key or null if not found
},
@ -148,7 +156,9 @@ export const addressInfoControllerAtom = atom({
export const addressInfoKeySelector = selectorFamily({
key: 'addressInfoKeySelector',
get: (key) => ({ get }) => {
get:
(key) =>
({ get }) => {
const userInfo = get(addressInfoControllerAtom);
return userInfo[key] || null; // Return the value for the key or null if not found
},
@ -178,3 +188,8 @@ export const groupsPropertiesAtom = atom({
key: 'groupsPropertiesAtom',
default: {},
});
export const isOpenBlockedModalAtom = atom({
key: 'isOpenBlockedModalAtom',
default: false,
});

View File

@ -3,15 +3,15 @@ import {
InputAdornment,
TextField,
TextFieldProps,
} from "@mui/material";
import React, { useRef, useState } from "react";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
} from '@mui/material';
import React, { useRef, useState } from 'react';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';
import {
removeTrailingZeros,
setNumberWithinBounds,
} from "./numberFunctions.ts";
import { CustomInput } from "../App-styles.ts";
} from './numberFunctions.ts';
import { CustomInput } from '../styles/App-styles.ts';
type eventType = React.ChangeEvent<HTMLInputElement>;
type BoundedNumericTextFieldProps = {
@ -37,18 +37,18 @@ export const BoundedNumericTextField = ({
...props
}: BoundedNumericTextFieldProps) => {
const [textFieldValue, setTextFieldValue] = useState<string>(
initialValue || ""
initialValue || ''
);
const ref = useRef<HTMLInputElement | null>(null);
const stringIsEmpty = (value: string) => {
return value === "";
return value === '';
};
const isAllZerosNum = /^0*\.?0*$/;
const isFloatNum = /^-?[0-9]*\.?[0-9]*$/;
const isIntegerNum = /^-?[0-9]+$/;
const skipMinMaxCheck = (value: string) => {
const lastIndexIsDecimal = value.charAt(value.length - 1) === ".";
const lastIndexIsDecimal = value.charAt(value.length - 1) === '.';
const isEmpty = stringIsEmpty(value);
const isAllZeros = isAllZerosNum.test(value);
const isInteger = isIntegerNum.test(value);
@ -69,7 +69,7 @@ export const BoundedNumericTextField = ({
const getSigDigits = (number: string) => {
if (isIntegerNum.test(number)) return 0;
const decimalSplit = number.split(".");
const decimalSplit = number.split('.');
return decimalSplit[decimalSplit.length - 1].length;
};
@ -78,15 +78,15 @@ export const BoundedNumericTextField = ({
};
const filterTypes = (value: string) => {
if (allowDecimals === false) value = value.replace(".", "");
if (allowNegatives === false) value = value.replace("-", "");
if (allowDecimals === false) value = value.replace('.', '');
if (allowNegatives === false) value = value.replace('-', '');
if (sigDigitsExceeded(value, maxSigDigits)) {
value = value.substring(0, value.length - 1);
}
return value;
};
const filterValue = (value: string) => {
if (stringIsEmpty(value)) return "";
if (stringIsEmpty(value)) return '';
value = filterTypes(value);
if (isFloatNum.test(value)) {
return setMinMaxValue(value);
@ -109,8 +109,8 @@ export const BoundedNumericTextField = ({
const formatValueOnBlur = (e: eventType) => {
let value = e.target.value;
if (stringIsEmpty(value) || value === ".") {
setTextFieldValue("");
if (stringIsEmpty(value) || value === '.') {
setTextFieldValue('');
return;
}
@ -129,23 +129,33 @@ export const BoundedNumericTextField = ({
...props?.InputProps,
endAdornment: addIconButtons ? (
<InputAdornment position="end">
<IconButton size="small" onClick={() => changeValueWithIncDecButton(1)}>
<AddIcon sx={{
color: 'white'
}} />{" "}
<IconButton
size="small"
onClick={() => changeValueWithIncDecButton(1)}
>
<AddIcon
sx={{
color: 'white',
}}
/>{' '}
</IconButton>
<IconButton size="small" onClick={() => changeValueWithIncDecButton(-1)}>
<RemoveIcon sx={{
color: 'white'
}} />{" "}
<IconButton
size="small"
onClick={() => changeValueWithIncDecButton(-1)}
>
<RemoveIcon
sx={{
color: 'white',
}}
/>{' '}
</IconButton>
</InputAdornment>
) : (
<></>
),
}}
onChange={e => listeners(e as eventType)}
onBlur={e => {
onChange={(e) => listeners(e as eventType)}
onBlur={(e) => {
formatValueOnBlur(e as eventType);
}}
autoComplete="off"

View File

@ -1,57 +1,57 @@
import React, { useState } from "react";
import QRCode from "react-qr-code";
import { TextP } from "../App-styles";
import { Box, Typography } from "@mui/material";
import React, { useState } from 'react';
import QRCode from 'react-qr-code';
import { TextP } from '../styles/App-styles';
import { Box, Typography } from '@mui/material';
export const AddressQRCode = ({ targetAddress }) => {
const [open, setOpen] = useState(false);
return (
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
flexDirection: "column",
marginTop: '10px'
display: 'flex',
gap: '10px',
alignItems: 'center',
flexDirection: 'column',
marginTop: '10px',
}}
>
<Typography
sx={{
cursor: "pointer",
fontSize: "14px",
cursor: 'pointer',
fontSize: '14px',
}}
onClick={() => {
setOpen((prev)=> !prev);
setOpen((prev) => !prev);
}}
>
{open ? 'Hide QR code' :'See QR code'}
{open ? 'Hide QR code' : 'See QR code'}
</Typography>
{open && (
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
justifyContent: "center",
width: "100%",
display: 'flex',
gap: '10px',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
gap: "10px",
width: "100%",
alignItems: "center",
flexDirection: "column",
marginTop: "20px",
display: 'flex',
gap: '10px',
width: '100%',
alignItems: 'center',
flexDirection: 'column',
marginTop: '20px',
}}
>
<TextP
sx={{
textAlign: "center",
textAlign: 'center',
lineHeight: 1.2,
fontSize: "16px",
fontSize: '16px',
fontWeight: 500,
}}
>

View File

@ -1,78 +1,78 @@
import { Typography, Box, ButtonBase } from "@mui/material";
import { styled } from "@mui/system";
import { Typography, Box, ButtonBase } from '@mui/material';
import { styled } from '@mui/system';
export const AppsParent = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "center",
overflow: "auto",
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100%',
alignItems: 'center',
overflow: 'auto',
// For WebKit-based browsers (Chrome, Safari, etc.)
"::-webkit-scrollbar": {
width: "0px", // Set the width to 0 to hide the scrollbar
height: "0px", // Set the height to 0 for horizontal scrollbar
'::-webkit-scrollbar': {
width: '0px', // Set the width to 0 to hide the scrollbar
height: '0px', // Set the height to 0 for horizontal scrollbar
},
// For Firefox
scrollbarWidth: "none", // Hides the scrollbar in Firefox
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,
}));
export const AppsContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: "space-evenly",
gap: "24px",
flexWrap: "wrap",
alignItems: "flex-start",
alignSelf: "center",
display: 'flex',
width: '90%',
justifyContent: 'space-evenly',
gap: '24px',
flexWrap: 'wrap',
alignItems: 'flex-start',
alignSelf: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
display: 'flex',
width: '100%',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
}));
export const AppsWidthLimiter = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "flex-start",
display: 'flex',
width: '90%',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'flex-start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: "space-between",
alignItems: "center",
borderRadius: "8px",
padding: "0px 10px",
height: "36px",
display: 'flex',
width: '90%',
justifyContent: 'space-between',
alignItems: 'center',
borderRadius: '8px',
padding: '0px 10px',
height: '36px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: "flex-start",
alignItems: "center",
gap: "10px",
display: 'flex',
width: '90%',
justifyContent: 'flex-start',
alignItems: 'center',
gap: '10px',
flexGrow: 1,
flexShrink: 0,
backgroundColor: theme.palette.background.default,
@ -80,51 +80,50 @@ export const AppsSearchLeft = styled(Box)(({ theme }) => ({
}));
export const AppsSearchRight = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: "flex-end",
alignItems: "center",
display: 'flex',
width: '90%',
justifyContent: 'flex-end',
alignItems: 'center',
flexShrink: 1,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppCircleContainer = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: "5px",
alignItems: "center",
width: "100%",
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 }) => ({
fontSize: "36px",
fontSize: '36px',
fontWeight: 500,
lineHeight: "43.57px",
textAlign: "left",
lineHeight: '43.57px',
textAlign: 'left',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
fontSize: "14px",
fontWeight: 500,
lineHeight: 1.2,
// whiteSpace: 'nowrap',
overflow: "hidden",
textOverflow: "ellipsis",
width: "120%",
"-webkit-line-clamp": "2",
"-webkit-box-orient": "vertical",
display: "-webkit-box",
'-webkit-box-orient': 'vertical',
'-webkit-line-clamp': '2',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: '-webkit-box',
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '120%',
}));
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
fontSize: "16px",
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
backgroundColor: theme.palette.background.default,
@ -132,58 +131,63 @@ export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
}));
export const AppCircle = styled(Box)(({ theme }) => ({
display: "flex",
width: "75px",
flexDirection: "column",
height: "75px",
alignItems: "center",
justifyContent: "center",
borderRadius: "50%",
alignItems: 'center',
backgroundColor: theme.palette.background.default,
borderColor:
theme.palette.mode === 'dark'
? 'rgb(209, 209, 209)'
: 'rgba(41, 41, 43, 1)',
borderWidth: '1px',
borderRadius: '50%',
borderStyle: 'solid',
color: theme.palette.text.primary,
border: "1px solid #FFFFFF",
display: 'flex',
flexDirection: 'column',
height: '75px',
justifyContent: 'center',
width: '75px',
}));
export const AppInfoSnippetContainer = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}));
export const AppInfoSnippetLeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
gap: "12px",
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
gap: '12px',
justifyContent: 'flex-start',
}));
export const AppInfoSnippetRight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppDownloadButton = styled(ButtonBase)(({ theme }) => ({
backgroundColor: "#247C0E",
backgroundColor: '#247C0E',
color: theme.palette.text.primary,
width: "101px",
height: "29px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "25px",
alignSelf: "center",
width: '101px',
height: '29px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px',
alignSelf: 'center',
}));
export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({
fontSize: "14px",
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
backgroundColor: theme.palette.background.default,
@ -191,50 +195,50 @@ export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({
}));
export const AppPublishTagsContainer = styled(Box)(({ theme }) => ({
gap: "10px",
flexWrap: "wrap",
justifyContent: "flex-start",
width: "100%",
display: "flex",
gap: '10px',
flexWrap: 'wrap',
justifyContent: 'flex-start',
width: '100%',
display: 'flex',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "flex-start",
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'flex-start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppInfoAppName = styled(Typography)(({ theme }) => ({
fontSize: "16px",
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
textAlign: "start",
textAlign: 'start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppInfoUserName = styled(Typography)(({ theme }) => ({
fontSize: "13px",
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.2,
textAlign: "start",
textAlign: 'start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsNavBarParent = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
height: "60px",
padding: "0px 10px",
position: "fixed",
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
height: '60px',
padding: '0px 10px',
position: 'fixed',
bottom: 0,
zIndex: 1,
backgroundColor: theme.palette.background.default,
@ -242,120 +246,120 @@ export const AppsNavBarParent = styled(Box)(({ theme }) => ({
}));
export const AppsNavBarLeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
flexGrow: 1,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsNavBarRight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TabParent = styled(Box)(({ theme }) => ({
height: "36px",
width: "36px",
position: "relative",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
height: '36px',
width: '36px',
position: 'relative',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppCTAParent = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppCTALeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppCTARight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({
width: "101px",
height: "29px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "25px",
border: "1px solid #FFFFFF",
width: '101px',
height: '29px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px',
border: '1px solid #FFFFFF',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "60px",
height: "60px",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '60px',
height: '60px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppInfo = styled(Typography)(({ theme }) => ({
fontSize: "10px",
fontSize: '10px',
fontWeight: 400,
lineHeight: 1.2,
fontStyle: "italic",
fontStyle: 'italic',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
width: "101px",
height: "30px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "5px",
width: '101px',
height: '30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '5px',
fontWeight: 600,
fontSize: "10px",
fontSize: '10px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsCategoryInfo = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
width: "100%",
display: 'flex',
alignItems: 'center',
width: '100%',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsCategoryInfoSub = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
fontSize: "12px",
fontSize: '12px',
fontWeight: 700,
lineHeight: 1.2,
backgroundColor: theme.palette.background.default,
@ -363,7 +367,7 @@ export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
}));
export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
fontSize: "12px",
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
backgroundColor: theme.palette.background.default,
@ -371,11 +375,11 @@ export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
}));
export const AppsInfoDescription = styled(Typography)(({ theme }) => ({
fontSize: "13px",
fontSize: '13px',
fontWeight: 300,
lineHeight: 1.2,
width: "90%",
textAlign: "start",
width: '90%',
textAlign: 'start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));

View File

@ -1,46 +1,68 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { AppsDevModeHome } from "./AppsDevModeHome";
import { Spacer } from "../../common/Spacer";
import { MyContext, getBaseApiReact } from "../../App";
import { AppInfo } from "./AppInfo";
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { AppsDevModeHome } from './AppsDevModeHome';
import { Spacer } from '../../common/Spacer';
import { MyContext, 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 } from "@mui/material";
import { HomeIcon } from "../../assets/Icons/HomeIcon";
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
import { Save } from "../Save/Save";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
import { AppsDevModeNavBar } from "./AppsDevModeNavBar";
import { CoreSyncStatus } from "../CoreSyncStatus";
import { AppsIcon } from "../../assets/Icons/AppsIcon";
import { IconWrapper } from "../Desktop/DesktopFooter";
} 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 } from '@mui/material';
import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { Save } from '../Save/Save';
import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { AppsDevModeNavBar } from './AppsDevModeNavBar';
import { AppsIcon } from '../../assets/Icons/AppsIcon';
import { IconWrapper } from '../Desktop/DesktopFooter';
const uid = new ShortUniqueId({ length: 8 });
export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktopSideView, hasUnreadDirects, isDirects, isGroups, hasUnreadGroups, toggleSideViewGroups, toggleSideViewDirects, setDesktopViewMode, desktopViewMode, isApps}) => {
export const AppsDevMode = ({
mode,
setMode,
show,
myName,
goToHome,
setDesktopSideView,
hasUnreadDirects,
isDirects,
isGroups,
hasUnreadGroups,
toggleSideViewGroups,
toggleSideViewDirects,
setDesktopViewMode,
desktopViewMode,
isApps,
}) => {
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({});
useEffect(() => {
setTimeout(() => {
executeEvent("appsDevModeSetTabsToNav", {
executeEvent('appsDevModeSetTabsToNav', {
data: {
tabs: tabs,
selectedTab: selectedTab,
@ -50,17 +72,16 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
}, 100);
}, [show, tabs, selectedTab, isNewTabWindow]);
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');
@ -78,17 +99,16 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
} else if (mode === 'publish') {
setMode('library');
}
} else if(selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
} else if (selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {});
}
};
useEffect(() => {
subscribeToEvent("devModeNavigateBack", navigateBackFunc);
subscribeToEvent('devModeNavigateBack', navigateBackFunc);
return () => {
unsubscribeFromEvent("devModeNavigateBack", navigateBackFunc);
unsubscribeFromEvent('devModeNavigateBack', navigateBackFunc);
};
}, [mode, selectedTab]);
@ -100,58 +120,52 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
};
setTabs((prev) => [...prev, newTab]);
setSelectedTab(newTab);
setMode("viewer");
setMode('viewer');
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("appsDevModeAddTab", addTabFunc);
subscribeToEvent('appsDevModeAddTab', addTabFunc);
return () => {
unsubscribeFromEvent("appsDevModeAddTab", addTabFunc);
unsubscribeFromEvent('appsDevModeAddTab', addTabFunc);
};
}, [tabs]);
const updateTabFunc = (e) => {
const data = e.detail?.data;
if(!data.tabId) return
const findIndexTab = tabs.findIndex((tab)=> tab?.tabId === data?.tabId)
if(findIndexTab === -1) return
const copyTabs = [...tabs]
const newTab ={
if (!data.tabId) return;
const findIndexTab = tabs.findIndex((tab) => tab?.tabId === data?.tabId);
if (findIndexTab === -1) return;
const copyTabs = [...tabs];
const newTab = {
...copyTabs[findIndexTab],
url: data.url
}
copyTabs[findIndexTab] = newTab
url: data.url,
};
copyTabs[findIndexTab] = newTab;
setTabs(copyTabs);
setSelectedTab(newTab);
setMode("viewer");
setMode('viewer');
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("appsDevModeUpdateTab", updateTabFunc);
subscribeToEvent('appsDevModeUpdateTab', updateTabFunc);
return () => {
unsubscribeFromEvent("appsDevModeUpdateTab", updateTabFunc);
unsubscribeFromEvent('appsDevModeUpdateTab', updateTabFunc);
};
}, [tabs]);
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
if(!e.detail?.isDevMode) return
if (!e.detail?.isDevMode) return;
setSelectedTab(data);
setTimeout(() => {
executeEvent("appsDevModeSetTabsToNav", {
executeEvent('appsDevModeSetTabsToNav', {
data: {
tabs: tabs,
selectedTab: data,
@ -162,12 +176,11 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("setSelectedTabDevMode", setSelectedTabFunc);
subscribeToEvent('setSelectedTabDevMode', setSelectedTabFunc);
return () => {
unsubscribeFromEvent("setSelectedTabDevMode", setSelectedTabFunc);
unsubscribeFromEvent('setSelectedTabDevMode', setSelectedTabFunc);
};
}, [tabs, isNewTabWindow]);
@ -175,14 +188,14 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
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("appsDevModeSetTabsToNav", {
executeEvent('appsDevModeSetTabsToNav', {
data: {
tabs: copyTabs,
selectedTab: copyTabs[0],
@ -192,93 +205,101 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
};
useEffect(() => {
subscribeToEvent("removeTabDevMode", removeTabFunc);
subscribeToEvent('removeTabDevMode', removeTabFunc);
return () => {
unsubscribeFromEvent("removeTabDevMode", removeTabFunc);
unsubscribeFromEvent('removeTabDevMode', removeTabFunc);
};
}, [tabs]);
const setNewTabWindowFunc = (e) => {
setIsNewTabWindow(true);
setSelectedTab(null)
setSelectedTab(null);
};
useEffect(() => {
subscribeToEvent("devModeNewTabWindow", setNewTabWindowFunc);
subscribeToEvent('devModeNewTabWindow', setNewTabWindowFunc);
return () => {
unsubscribeFromEvent("devModeNewTabWindow", setNewTabWindowFunc);
unsubscribeFromEvent('devModeNewTabWindow', setNewTabWindowFunc);
};
}, [tabs]);
return (
<AppsParent
sx={{
flexDirection: 'row' ,
flexDirection: 'row',
position: !show && 'fixed',
left: !show && '-200vw',
}}
>
<Box sx={{
<Box
sx={{
width: '60px',
flexDirection: 'column',
height: '100vh',
alignItems: 'center',
display: 'flex',
gap: '25px'
}}>
gap: '25px',
}}
>
<ButtonBase
sx={{
width: '60px',
height: '60px',
paddingTop: '23px'
paddingTop: '23px',
}}
onClick={() => {
goToHome();
}}
>
<HomeIcon
height={34}
color={desktopViewMode === 'home' ? 'white': "rgba(250, 250, 250, 0.5)"}
color={
desktopViewMode === 'home' ? 'white' : 'rgba(250, 250, 250, 0.5)'
}
/>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('apps')
setDesktopViewMode('apps');
}}
>
<IconWrapper
color={isApps ? 'white' :"rgba(250, 250, 250, 0.5)"}
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
label="Apps"
disableWidth
>
<AppsIcon height={30} color={isApps ? 'white' :"rgba(250, 250, 250, 0.5)"} />
<AppsIcon
height={30}
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('chat')
setDesktopViewMode('chat');
}}
>
<IconWrapper
color={(hasUnreadDirects || hasUnreadGroups) ? "var(--unread)" : desktopViewMode === 'chat' ? 'white' :"rgba(250, 250, 250, 0.5)"}
color={
hasUnreadDirects || hasUnreadGroups
? 'var(--unread)'
: desktopViewMode === 'chat'
? 'white'
: 'rgba(250, 250, 250, 0.5)'
}
label="Chat"
disableWidth
>
<MessagingIcon
height={30}
color={
(hasUnreadDirects || hasUnreadGroups)
? "var(--unread)"
hasUnreadDirects || hasUnreadGroups
? 'var(--unread)'
: desktopViewMode === 'chat'
? "white"
: "rgba(250, 250, 250, 0.5)"
? 'white'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>
@ -286,42 +307,48 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
<Save isDesktop disableWidth myName={myName} />
<ButtonBase
onClick={() => {
setDesktopViewMode('dev')
setDesktopViewMode('dev');
}}
>
<IconWrapper
color={desktopViewMode === 'dev' ? 'white' : "rgba(250, 250, 250, 0.5)"}
color={
desktopViewMode === 'dev' ? 'white' : 'rgba(250, 250, 250, 0.5)'
}
label="Dev"
disableWidth
>
<AppsIcon color={desktopViewMode === 'dev' ? 'white' : "rgba(250, 250, 250, 0.5)"} height={30} />
<AppsIcon
color={
desktopViewMode === 'dev' ? 'white' : 'rgba(250, 250, 250, 0.5)'
}
height={30}
/>
</IconWrapper>
</ButtonBase>
{mode !== 'home' && (
<AppsDevModeNavBar />
)}
{mode !== 'home' && <AppsDevModeNavBar />}
</Box>
{mode === "home" && (
<Box sx={{
{mode === 'home' && (
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto'
}}>
overflow: 'auto',
}}
>
<Spacer height="30px" />
<AppsDevModeHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
<AppsDevModeHome
myName={myName}
availableQapps={availableQapps}
setMode={setMode}
myApp={null}
myWebsite={null}
/>
</Box>
)}
{tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef();
@ -338,18 +365,25 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
);
})}
{isNewTabWindow && mode === "viewer" && (
{isNewTabWindow && mode === 'viewer' && (
<>
<Box sx={{
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto'
}}>
overflow: 'auto',
}}
>
<Spacer height="30px" />
<AppsDevModeHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
<AppsDevModeHome
myName={myName}
availableQapps={availableQapps}
setMode={setMode}
myApp={null}
myWebsite={null}
/>
</Box>
</>
)}

View File

@ -1,22 +1,21 @@
import React from "react";
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 { 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';
export const AppsDevModeTabComponent = ({ isSelected, app }) => {
return (
<ButtonBase
onClick={() => {
if (isSelected) {
executeEvent("removeTabDevMode", {
executeEvent('removeTabDevMode', {
data: app,
});
return;
}
executeEvent("setSelectedTabDevMode", {
executeEvent('setSelectedTabDevMode', {
data: app,
isDevMode: true,
});
@ -24,15 +23,15 @@ export const AppsDevModeTabComponent = ({ isSelected, app }) => {
>
<TabParent
sx={{
border: isSelected && "1px solid #FFFFFF",
border: isSelected && '1px solid #FFFFFF',
}}
>
{isSelected && (
<img
style={{
position: "absolute",
top: "-5px",
right: "-5px",
position: 'absolute',
top: '-5px',
right: '-5px',
zIndex: 1,
}}
src={NavCloseTab}
@ -40,19 +39,21 @@ export const AppsDevModeTabComponent = ({ isSelected, app }) => {
)}
<Avatar
sx={{
height: "28px",
width: "28px",
height: '28px',
width: '28px',
}}
alt=""
src={``}
>
<img
style={{
width: "28px",
height: "auto",
width: '28px',
height: 'auto',
}}
src={ app?.customIcon ? app?.customIcon :
app?.service
src={
app?.customIcon
? app?.customIcon
: app?.service
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@ -6,42 +6,38 @@ import {
AppLibrarySubTitle,
AppsContainer,
AppsParent,
} from "./Apps-styles";
import { Avatar, ButtonBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
import { SortablePinnedApps } from "./SortablePinnedApps";
import { Spacer } from "../../common/Spacer";
} from './Apps-styles';
import { Avatar, ButtonBase } from '@mui/material';
import { Add } from '@mui/icons-material';
import { getBaseApiReact, isMobile } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import { SortablePinnedApps } from './SortablePinnedApps';
import { Spacer } from '../../common/Spacer';
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => {
return (
<>
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<AppLibrarySubTitle
>
Apps Dashboard
</AppLibrarySubTitle>
<AppLibrarySubTitle>Apps Dashboard</AppLibrarySubTitle>
</AppsContainer>
<Spacer height="20px" />
<AppsContainer>
<ButtonBase
onClick={() => {
setMode("library");
setMode('library');
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircleContainer sx={{
gap: !isMobile ? "10px" : "5px",
}}>
<AppCircle>
<Add>+</Add>
</AppCircle>
@ -49,8 +45,11 @@ export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => {
</AppCircleContainer>
</ButtonBase>
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />
<SortablePinnedApps
availableQapps={availableQapps}
myWebsite={myWebsite}
myApp={myApp}
/>
</AppsContainer>
</>
);

View File

@ -1,21 +1,22 @@
import React, { useState } from "react";
import React, { useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
} from "./Apps-styles";
import { Box, ButtonBase, Input } from "@mui/material";
import { Add } from "@mui/icons-material";
import { isMobile } from "../../App";
import { executeEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
import { SortablePinnedApps } from "./SortablePinnedApps";
import { extractComponents } from "../Chat/MessageDisplay";
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
import { AppsPrivate } from "./AppsPrivate";
import ThemeSelector from "../Theme/ThemeSelector";
} from './Apps-styles';
import { Box, ButtonBase, Input, useTheme } from '@mui/material';
import { Add } from '@mui/icons-material';
import { isMobile } from '../../App';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import { SortablePinnedApps } from './SortablePinnedApps';
import { extractComponents } from '../Chat/MessageDisplay';
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
import { AppsPrivate } from './AppsPrivate';
import ThemeSelector from '../Theme/ThemeSelector';
export const AppsHomeDesktop = ({
setMode,
myApp,
@ -23,7 +24,8 @@ export const AppsHomeDesktop = ({
availableQapps,
myName,
}) => {
const [qortalUrl, setQortalUrl] = useState("");
const [qortalUrl, setQortalUrl] = useState('');
const theme = useTheme();
const openQortalUrl = () => {
try {
@ -31,22 +33,24 @@ export const AppsHomeDesktop = ({
const res = extractComponents(qortalUrl);
if (res) {
const { service, name, identifier, path } = res;
executeEvent("addTab", { data: { service, name, identifier, path } });
executeEvent("open-apps-mode", {});
setQortalUrl("qortal://");
executeEvent('addTab', { data: { service, name, identifier, path } });
executeEvent('open-apps-mode', {});
setQortalUrl('qortal://');
}
} catch (error) {
console.log(error);
}
} catch (error) {}
};
return (
<>
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Apps Dashboard
@ -57,19 +61,22 @@ export const AppsHomeDesktop = ({
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
backgroundColor: "#1f2023",
padding: "7px",
borderRadius: "20px",
width: "100%",
maxWidth: "500px",
display: 'flex',
gap: '20px',
alignItems: 'center',
backgroundColor:
theme.palette.mode === 'dark'
? 'rgba(41, 41, 43, 1)'
: 'rgb(209, 209, 209)',
padding: '7px',
borderRadius: '20px',
width: '100%',
maxWidth: '500px',
}}
>
<Input
@ -83,24 +90,24 @@ export const AppsHomeDesktop = ({
autoCorrect="off"
placeholder="qortal://"
sx={{
width: "100%",
color: "white",
"& .MuiInput-input::placeholder": {
color: "rgba(84, 84, 84, 0.70) !important",
fontSize: "20px",
fontStyle: "normal",
width: '100%',
color: theme.palette.mode === 'dark' ? 'white' : 'black',
'& .MuiInput-input::placeholder': {
color: 'rgba(84, 84, 84, 0.70) !important',
fontSize: '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
}}
onKeyDown={(e) => {
if (e.key === "Enter" && qortalUrl) {
if (e.key === 'Enter' && qortalUrl) {
openQortalUrl();
}
}}
@ -108,7 +115,7 @@ export const AppsHomeDesktop = ({
<ButtonBase onClick={() => openQortalUrl()}>
<ArrowOutwardIcon
sx={{
color: qortalUrl ? "white" : "rgba(84, 84, 84, 0.70)",
color: qortalUrl ? 'white' : 'rgba(84, 84, 84, 0.70)',
}}
/>
</ButtonBase>
@ -119,18 +126,18 @@ export const AppsHomeDesktop = ({
<AppsContainer
sx={{
gap: "50px",
justifyContent: "flex-start",
gap: '50px',
justifyContent: 'flex-start',
}}
>
<ButtonBase
onClick={() => {
setMode("library");
setMode('library');
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: !isMobile ? '10px' : '5px',
}}
>
<AppCircle>
@ -150,7 +157,7 @@ export const AppsHomeDesktop = ({
/>
</AppsContainer>
<ThemeSelector style={{ position: "fixed", bottom: "1%", left: "0%" }} />
<ThemeSelector style={{ position: 'fixed', bottom: '1%', left: '0%' }} />
</>
);
};

View File

@ -1,4 +1,11 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
AppCircle,
AppCircleContainer,
@ -16,84 +23,97 @@ 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";
import ReturnSVG from '../../assets/svgs/Return.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 ReturnSVG from '../../assets/svgs/Return.svg';
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles";
import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from 'react-virtuoso';
import { executeEvent } from '../../utils/events';
import {
ComposeP,
MailIconImg,
ShowMessageReturnButton,
} from '../Group/Forum/Mail-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',
];
// TODO: apply dark/light style
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')({
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 AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => {
const [searchValue, setSearchValue] = useState("");
export const AppsLibrary = ({
availableQapps,
setMode,
myName,
hasPublishApp,
isShow,
categories = { categories },
}) => {
const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
const officialApps = useMemo(() => {
return availableQapps.filter(
(app) =>
app.service === "APP" &&
app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
@ -117,23 +137,28 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
}, [debouncedValue]);
const rowRenderer = (index) => {
let app = searchedList[index];
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} />;
return (
<AppInfoSnippet
key={`${app?.service}-${app?.name}`}
app={app}
myName={myName}
/>
);
};
return (
<AppsLibraryContainer sx={{
display: !isShow && 'none'
}}>
<AppsLibraryContainer
sx={{
display: !isShow && 'none',
}}
>
<AppsWidthLimiter>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "center",
display: 'flex',
width: '100%',
justifyContent: 'center',
}}
>
<AppsSearchContainer>
@ -145,8 +170,8 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
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,
}}
/>
@ -155,7 +180,7 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
{searchValue && (
<ButtonBase
onClick={() => {
setSearchValue("");
setSearchValue('');
}}
>
<img src={IconClearInput} />
@ -166,21 +191,25 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
</Box>
</AppsWidthLimiter>
<Spacer height="25px" />
<ShowMessageReturnButton sx={{
padding: '2px'
}} onClick={() => {
executeEvent("navigateBack", {});
}}>
<ShowMessageReturnButton
sx={{
padding: '2px',
}}
onClick={() => {
executeEvent('navigateBack', {});
}}
>
<MailIconImg src={ReturnSVG} />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<AppsWidthLimiter>
<StyledVirtuosoContainer sx={{
height: rootHeight
}}>
<StyledVirtuosoContainer
sx={{
height: rootHeight,
}}
>
<Virtuoso
ref={virtuosoRef}
data={searchedList}
@ -188,7 +217,7 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
atBottomThreshold={50}
followOutput="smooth"
components={{
Scroller: ScrollerStyled // Use the styled scroller component
Scroller: ScrollerStyled, // Use the styled scroller component
}}
/>
</StyledVirtuosoContainer>
@ -203,14 +232,14 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
return (
<ButtonBase
sx={{
height: "80px",
width: "60px",
height: '80px',
width: '60px',
}}
onClick={()=> {
onClick={() => {
// executeEvent("addTab", {
// data: qapp
// })
executeEvent("selectedAppInfo", {
executeEvent('selectedAppInfo', {
data: qapp,
});
}}
@ -218,13 +247,13 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
border: 'none',
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
height: '31px',
width: '31px',
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -233,8 +262,8 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
@ -250,21 +279,24 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
})}
</AppsContainer>
<Spacer height="30px" />
<AppLibrarySubTitle>{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}</AppLibrarySubTitle>
<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')
}}>
<PublishQAppCTARight
onClick={() => {
setMode('publish');
}}
>
<PublishQAppCTAButton>
{hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton>
@ -272,50 +304,57 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
</PublishQAppCTARight>
</PublishQAppCTAParent>
<AppsWidthLimiter>
<Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsWidthLimiter sx={{
<AppsWidthLimiter
sx={{
flexDirection: 'row',
overflowX: 'auto',
width: '100%',
gap: '5px',
"::-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",
}}>
{categories?.map((category)=> {
'-msOverflowStyle': 'none',
}}
>
{categories?.map((category) => {
return (
<ButtonBase key={category?.id} onClick={()=> {
<ButtonBase
key={category?.id}
onClick={() => {
executeEvent('selectedCategory', {
data: category
})
}}>
<Box sx={{
data: category,
});
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '110px',
width: '110px',
background: 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
background:
'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
color: '#1D1D1E',
fontWeight: 700,
fontSize: '16px',
flexShrink: 0,
borderRadius: '11px'
}}>
borderRadius: '11px',
}}
>
{category?.name}
</Box>
</ButtonBase>
)
);
})}
</AppsWidthLimiter>
</AppsWidthLimiter>

View File

@ -5,7 +5,7 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
} from 'react';
import {
AppCircle,
AppCircleContainer,
@ -23,77 +23,87 @@ import {
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, Typography, 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 qappLibraryText from "../../assets/svgs/qappLibraryText.svg";
import RefreshIcon from "@mui/icons-material/Refresh";
} from './Apps-styles';
import {
Avatar,
Box,
ButtonBase,
InputBase,
Typography,
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 qappLibraryText from '../../assets/svgs/qappLibraryText.svg';
import RefreshIcon from '@mui/icons-material/Refresh';
import qappDots from "../../assets/svgs/qappDots.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 { 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 { AppsNavBarDesktop } from "./AppsNavBarDesktop";
import ReturnSVG from '../../assets/svgs/Return.svg'
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles";
} from './AppsDesktop-styles';
import ReturnSVG from '../../assets/svgs/Return.svg';
import {
ComposeP,
MailIconImg,
ShowMessageReturnButton,
} from '../Group/Forum/Mail-styles';
const officialAppList = [
"q-tube",
"q-blog",
"q-share",
"q-support",
"q-mail",
"q-fund",
"q-shop",
"q-trade",
"q-support",
"q-manager",
"q-mintership",
"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-mintership',
'q-wallets',
'q-search',
];
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 AppsLibraryDesktop = ({
@ -103,20 +113,20 @@ export const AppsLibraryDesktop = ({
hasPublishApp,
isShow,
categories,
getQapps
getQapps,
}) => {
const [searchValue, setSearchValue] = useState("");
const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef();
const officialApps = useMemo(() => {
return availableQapps.filter(
(app) =>
app.service === "APP" &&
app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
@ -125,7 +135,7 @@ export const AppsLibraryDesktop = ({
}, 350);
setTimeout(() => {
virtuosoRef.current.scrollToIndex({
index: 0
index: 0,
});
}, 500);
// Cleanup timeout if searchValue changes before the timeout completes
@ -138,8 +148,13 @@ export const AppsLibraryDesktop = ({
const searchedList = useMemo(() => {
if (!debouncedValue) return [];
return availableQapps.filter((app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
return availableQapps.filter(
(app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) ||
(app?.metadata?.title &&
app?.metadata?.title
?.toLowerCase()
.includes(debouncedValue.toLowerCase()))
);
}, [debouncedValue]);
@ -151,7 +166,7 @@ export const AppsLibraryDesktop = ({
app={app}
myName={myName}
parentStyles={{
padding: '0px 10px'
padding: '0px 10px',
}}
/>
);
@ -160,37 +175,38 @@ export const AppsLibraryDesktop = ({
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>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "space-between",
display: 'flex',
width: '100%',
justifyContent: 'space-between',
}}
>
<img src={qappLibraryText} />
<Box sx={{
<Box
sx={{
display: 'flex',
gap: '20px',
alignItems: 'center'
}}>
alignItems: 'center',
}}
>
<AppsSearchContainer
sx={{
width: "412px",
width: '412px',
}}
>
<AppsSearchLeft>
@ -201,8 +217,8 @@ export const AppsLibraryDesktop = ({
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,
}}
/>
@ -211,7 +227,7 @@ export const AppsLibraryDesktop = ({
{searchValue && (
<ButtonBase
onClick={() => {
setSearchValue("");
setSearchValue('');
}}
>
<img src={IconClearInput} />
@ -221,46 +237,46 @@ export const AppsLibraryDesktop = ({
</AppsSearchContainer>
<ButtonBase
onClick={(e) => {
getQapps()
getQapps();
}}
>
<RefreshIcon
sx={{
color: "rgba(250, 250, 250, 0.5)",
color: 'rgba(250, 250, 250, 0.5)',
width: '40px',
height: 'auto'
height: 'auto',
}}
/>
</ButtonBase>
</Box>
</Box>
</AppsWidthLimiter>
</AppsDesktopLibraryHeader>
<AppsDesktopLibraryBody
sx={{
height: `calc(100vh - 36px)`,
overflow: "auto",
padding: "0px",
alignItems: "center",
overflow: 'auto',
padding: '0px',
alignItems: 'center',
}}
>
<AppsDesktopLibraryBody
sx={{
height: `calc(100vh - 36px)`,
flexGrow: "unset",
maxWidth: "1500px",
width: "90%",
flexGrow: 'unset',
maxWidth: '1500px',
width: '90%',
}}
>
<Spacer height="70px" />
<ShowMessageReturnButton sx={{
padding: '2px'
}} onClick={() => {
executeEvent("navigateBack", {});
}}>
<ShowMessageReturnButton
sx={{
padding: '2px',
}}
onClick={() => {
executeEvent('navigateBack', {});
}}
>
<MailIconImg src={ReturnSVG} />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
@ -292,46 +308,48 @@ export const AppsLibraryDesktop = ({
<>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Official Apps
</AppLibrarySubTitle>
<Spacer height="45px" />
<AppsContainer sx={{
<AppsContainer
sx={{
gap: '50px',
justifyContent: 'flex-start'
}}>
justifyContent: 'flex-start',
}}
>
{officialApps?.map((qapp) => {
return (
<ButtonBase
sx={{
// height: "80px",
width: "80px",
width: '80px',
}}
onClick={() => {
// executeEvent("addTab", {
// data: qapp
// })
executeEvent("selectedAppInfo", {
executeEvent('selectedAppInfo', {
data: qapp,
});
}}
>
<AppCircleContainer
sx={{
gap: "10px",
gap: '10px',
}}
>
<AppCircle
sx={{
border: "none",
border: 'none',
}}
>
<Avatar
sx={{
height: "42px",
width: "42px",
height: '42px',
width: '42px',
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -340,8 +358,8 @@ export const AppsLibraryDesktop = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
@ -359,30 +377,30 @@ export const AppsLibraryDesktop = ({
<Spacer height="80px" />
<Box
sx={{
width: "100%",
gap: "250px",
display: "flex",
width: '100%',
gap: '250px',
display: 'flex',
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
width: "100%",
textAlign: "start",
fontSize: '30px',
width: '100%',
textAlign: 'start',
}}
>
{hasPublishApp ? "Update Apps!" : "Create Apps!"}
{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
</AppLibrarySubTitle>
<Spacer height="18px" />
<PublishQAppCTAParent
sx={{
gap: "25px",
gap: '25px',
}}
>
<PublishQAppCTALeft>
@ -394,11 +412,11 @@ export const AppsLibraryDesktop = ({
</PublishQAppCTALeft>
<PublishQAppCTARight
onClick={() => {
setMode("publish");
setMode('publish');
}}
>
<PublishQAppCTAButton>
{hasPublishApp ? "Update" : "Publish"}
{hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton>
<Spacer width="20px" />
</PublishQAppCTARight>
@ -406,13 +424,13 @@ export const AppsLibraryDesktop = ({
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Categories
@ -420,33 +438,32 @@ export const AppsLibraryDesktop = ({
<Spacer height="18px" />
<Box
sx={{
width: "100%",
display: "flex",
gap: "20px",
flexWrap: "wrap",
width: '100%',
display: 'flex',
gap: '20px',
flexWrap: 'wrap',
}}
>
<ButtonBase
onClick={() => {
executeEvent("selectedCategory", {
executeEvent('selectedCategory', {
data: {
id: 'all',
name: 'All'
name: 'All',
},
});
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "60px",
padding: "0px 24px",
border: "4px solid #10242F",
borderRadius: "6px",
boxShadow: "2px 4px 0px 0px #000000",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '60px',
padding: '0px 24px',
border: '4px solid #10242F',
borderRadius: '6px',
boxShadow: '2px 4px 0px 0px #000000',
}}
>
All
@ -457,21 +474,21 @@ export const AppsLibraryDesktop = ({
<ButtonBase
key={category?.id}
onClick={() => {
executeEvent("selectedCategory", {
executeEvent('selectedCategory', {
data: category,
});
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "60px",
padding: "0px 24px",
border: "4px solid #10242F",
borderRadius: "6px",
boxShadow: "2px 4px 0px 0px #000000",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '60px',
padding: '0px 24px',
border: '4px solid #10242F',
borderRadius: '6px',
boxShadow: '2px 4px 0px 0px #000000',
}}
>
{category?.name}

View File

@ -1,18 +1,32 @@
import React, { useMemo, useRef, useState } from "react";
import TipTap from "./TipTap";
import { AuthenticatedContainerInnerTop, CustomButton } from "../../App-styles";
import { Box, CircularProgress } from "@mui/material";
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
import ShortUniqueId from "short-unique-id";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { getBaseApi, getFee } from "../../background";
import { decryptPublishes, getTempPublish, handleUnencryptedPublishes, saveTempPublish } from "./GroupAnnouncements";
import { AnnouncementList } from "./AnnouncementList";
import { Spacer } from "../../common/Spacer";
import React, { useMemo, useRef, useState } from 'react';
import TipTap from './TipTap';
import {
AuthenticatedContainerInnerTop,
CustomButton,
} from '../../styles/App-styles';
import { Box, CircularProgress } from '@mui/material';
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import ShortUniqueId from 'short-unique-id';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getBaseApi, getFee } from '../../background';
import {
decryptPublishes,
getTempPublish,
handleUnencryptedPublishes,
saveTempPublish,
} from './GroupAnnouncements';
import { AnnouncementList } from './AnnouncementList';
import { Spacer } from '../../common/Spacer';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { getArbitraryEndpointReact, getBaseApiReact, isMobile, pauseAllQueues, resumeAllQueues } from "../../App";
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
const tempKey = 'accouncement-comment'
const tempKey = 'accouncement-comment';
const uid = new ShortUniqueId({ length: 8 });
export const AnnouncementDiscussion = ({
@ -23,16 +37,16 @@ export const AnnouncementDiscussion = ({
setSelectedAnnouncement,
show,
myName,
isPrivate
isPrivate,
}) => {
const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [comments, setComments] = useState([])
const [tempPublishedList, setTempPublishedList] = useState([])
const firstMountRef = useRef(false)
const [data, setData] = useState({})
const [comments, setComments] = useState([]);
const [tempPublishedList, setTempPublishedList] = useState([]);
const firstMountRef = useRef(false);
const [data, setData] = useState({});
const editorRef = useRef(null);
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
@ -41,10 +55,10 @@ export const AnnouncementDiscussion = ({
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if(isMobile){
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false)
setIsFocusedParent(false);
}, 200);
}
}
@ -52,13 +66,15 @@ export const AnnouncementDiscussion = ({
const getData = async ({ identifier, name }, isPrivate) => {
try {
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
if(!res?.ok) return
if (!res?.ok) return;
const data = await res.text();
const response = isPrivate === false ? handleUnencryptedPublishes([data]) : await decryptPublishes([{ data }], secretKey);
const response =
isPrivate === false
? handleUnencryptedPublishes([data])
: await decryptPublishes([{ data }], secretKey);
const messageData = response[0];
setData((prev) => {
@ -67,7 +83,6 @@ export const AnnouncementDiscussion = ({
[`${identifier}-${name}`]: messageData,
};
});
} catch (error) {}
};
@ -76,7 +91,8 @@ export const AnnouncementDiscussion = ({
if (!selectedAnnouncement) return;
return new Promise((res, rej) => {
window.sendMessage("publishGroupEncryptedResource", {
window
.sendMessage('publishGroupEncryptedResource', {
encryptedData,
identifier,
})
@ -88,63 +104,60 @@ export const AnnouncementDiscussion = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
};
const setTempData = async ()=> {
const setTempData = async () => {
try {
const getTempAnnouncements = await getTempPublish()
if(getTempAnnouncements[tempKey]){
let tempData = []
Object.keys(getTempAnnouncements[tempKey] || {}).map((key)=> {
const value = getTempAnnouncements[tempKey][key]
if(value.data?.announcementId === selectedAnnouncement.identifier){
tempData.push(value.data)
const getTempAnnouncements = await getTempPublish();
if (getTempAnnouncements[tempKey]) {
let tempData = [];
Object.keys(getTempAnnouncements[tempKey] || {}).map((key) => {
const value = getTempAnnouncements[tempKey][key];
if (value.data?.announcementId === selectedAnnouncement.identifier) {
tempData.push(value.data);
}
})
setTempPublishedList(tempData)
}
} catch (error) {
}
});
setTempPublishedList(tempData);
}
} catch (error) {}
};
const publishComment = async () => {
try {
pauseAllQueues()
const fee = await getFee('ARBITRARY')
pauseAllQueues();
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to perform a ARBITRARY transaction?" ,
publishFee: fee.fee + ' QORT'
})
message: 'Would you like to perform a ARBITRARY transaction?',
publishFee: fee.fee + ' QORT',
});
if (isSending) return;
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
if (!htmlContent?.trim() || htmlContent?.trim() === "<p></p>") return;
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return;
setIsSending(true);
const message = {
version: 1,
extra: {},
message: htmlContent,
};
const secretKeyObject = isPrivate === false ? null : await getSecretKey(false, true);
const secretKeyObject =
isPrivate === false ? null : await getSecretKey(false, true);
const message64: any = await objectToBase64(message);
const encryptSingle = isPrivate === false ? message64 : await encryptChatMessage(
message64,
secretKeyObject
);
const encryptSingle =
isPrivate === false
? message64
: await encryptChatMessage(message64, secretKeyObject);
const randomUid = uid.rnd();
const identifier = `cm-${selectedAnnouncement.identifier}-${randomUid}`;
const res = await publishAnc({
encryptedData: encryptSingle,
identifier
identifier,
});
const dataToSaveToStorage = {
@ -153,10 +166,10 @@ export const AnnouncementDiscussion = ({
service: 'DOCUMENT',
tempData: message,
created: Date.now(),
announcementId: selectedAnnouncement.identifier
}
await saveTempPublish({data: dataToSaveToStorage, key: tempKey})
setTempData()
announcementId: selectedAnnouncement.identifier,
};
await saveTempPublish({ data: dataToSaveToStorage, key: tempKey });
setTempData();
clearEditorContent();
}
@ -164,7 +177,7 @@ export const AnnouncementDiscussion = ({
} catch (error) {
console.error(error);
} finally {
resumeAllQueues()
resumeAllQueues();
setIsSending(false);
}
};
@ -172,7 +185,6 @@ export const AnnouncementDiscussion = ({
const getComments = React.useCallback(
async (selectedAnnouncement, isPrivate) => {
try {
setIsLoading(true);
const offset = 0;
@ -181,13 +193,13 @@ export const AnnouncementDiscussion = ({
const identifier = `cm-${selectedAnnouncement.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
setTempData()
setTempData();
setComments(responseData);
setIsLoading(false);
for (const data of responseData) {
@ -203,31 +215,28 @@ export const AnnouncementDiscussion = ({
[secretKey]
);
const loadMore = async()=> {
const loadMore = async () => {
try {
setIsLoading(true);
const offset = comments.length
const offset = comments.length;
const identifier = `cm-${selectedAnnouncement.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
setComments((prev)=> [...prev, ...responseData]);
setComments((prev) => [...prev, ...responseData]);
setIsLoading(false);
for (const data of responseData) {
getData({ name: data.name, identifier: data.identifier }, isPrivate);
}
} catch (error) {
}
}
} catch (error) {}
};
const combinedListTempAndReal = useMemo(() => {
// Combine the two lists
@ -235,87 +244,93 @@ export const AnnouncementDiscussion = ({
// Remove duplicates based on the "identifier"
const uniqueItems = new Map();
combined.forEach(item => {
combined.forEach((item) => {
uniqueItems.set(item.identifier, item); // This will overwrite duplicates, keeping the last occurrence
});
// Convert the map back to an array and sort by "created" timestamp in descending order
const sortedList = Array.from(uniqueItems.values()).sort((a, b) => b.created - a.created);
const sortedList = Array.from(uniqueItems.values()).sort(
(a, b) => b.created - a.created
);
return sortedList;
}, [tempPublishedList, comments]);
React.useEffect(() => {
if(!secretKey && isPrivate) return
if (!secretKey && isPrivate) return;
if (selectedAnnouncement && !firstMountRef.current && isPrivate !== null) {
getComments(selectedAnnouncement, isPrivate);
firstMountRef.current = true
firstMountRef.current = true;
}
}, [selectedAnnouncement, secretKey, isPrivate]);
return (
<div
style={{
height: isMobile ? '100%' : "100%",
display: "flex",
flexDirection: "column",
width: "100%",
height: isMobile ? '100%' : '100%',
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
<div style={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
<div
style={{
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
}}>
<AuthenticatedContainerInnerTop sx={{
height: '20px'
}}>
<ArrowBackIcon onClick={()=> setSelectedAnnouncement(null)} sx={{
cursor: 'pointer'
}} />
}}
>
<AuthenticatedContainerInnerTop
sx={{
height: '20px',
}}
>
<ArrowBackIcon
onClick={() => setSelectedAnnouncement(null)}
sx={{
cursor: 'pointer',
}}
/>
</AuthenticatedContainerInnerTop>
<Spacer height="20px" />
</div>
<AnnouncementList
announcementData={data}
initialMessages={combinedListTempAndReal}
setSelectedAnnouncement={()=> {}}
setSelectedAnnouncement={() => {}}
disableComment
showLoadMore={comments.length > 0 && comments.length % 20 === 0}
loadMore={loadMore}
myName={myName}
/>
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: isMobile ? "0px" : "150px",
maxHeight: isMobile ? "auto" : "400px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
width: "100%",
boxSizing: "border-box",
padding: isMobile ? "10px": "20px",
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink:0,
flexShrink: 0,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
// height: '100%',
flexGrow: isMobile && 1,
overflow: "auto",
overflow: 'auto',
}}
>
<TipTap
@ -323,24 +338,26 @@ export const AnnouncementDiscussion = ({
onEnter={publishComment}
disableEnter
maxHeightOffset="60px"
isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent}
isFocusedParent={isFocusedParent}
setIsFocusedParent={setIsFocusedParent}
/>
</div>
<Box sx={{
<Box
sx={{
display: 'flex',
width: '100&',
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
}}>
}}
>
{isFocusedParent && (
<CustomButton
onClick={()=> {
if(isSending) return
setIsFocusedParent(false)
clearEditorContent()
onClick={() => {
if (isSending) return;
setIsFocusedParent(false);
clearEditorContent();
// Unfocus the editor
}}
style={{
@ -353,10 +370,8 @@ export const AnnouncementDiscussion = ({
background: 'red',
}}
>
{` Close`}
</CustomButton>
)}
<CustomButton
onClick={() => {
@ -364,38 +379,37 @@ export const AnnouncementDiscussion = ({
publishComment();
}}
style={{
marginTop: "auto",
alignSelf: "center",
cursor: isSending ? "default" : "pointer",
background: isSending && "rgba(0, 0, 0, 0.8)",
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0,
padding: isMobile && '5px',
fontSize: isMobile && '14px'
fontSize: isMobile && '14px',
}}
>
{isSending && (
<CircularProgress
size={18}
sx={{
position: "absolute",
top: "50%",
left: "50%",
marginTop: "-12px",
marginLeft: "-12px",
color: "white",
position: 'absolute',
top: '50%',
left: '50%',
marginTop: '-12px',
marginLeft: '-12px',
color: 'white',
}}
/>
)}
{` Publish Comment`}
</CustomButton>
</Box>
</div>
<LoadingSnackbar
open={isLoading}
info={{
message: "Loading comments... please wait.",
message: 'Loading comments... please wait.',
}}
/>
</div>

View File

@ -1,13 +1,13 @@
import React, { useCallback, useState, useEffect, useRef } from "react";
import React, { useCallback, useState, useEffect, useRef } from 'react';
import {
List,
AutoSizer,
CellMeasurerCache,
CellMeasurer,
} from "react-virtualized";
import { AnnouncementItem } from "./AnnouncementItem";
import { Box } from "@mui/material";
import { CustomButton } from "../../App-styles";
} from 'react-virtualized';
import { AnnouncementItem } from './AnnouncementItem';
import { Box } from '@mui/material';
import { CustomButton } from '../../styles/App-styles';
const cache = new CellMeasurerCache({
fixedWidth: true,
@ -21,9 +21,8 @@ export const AnnouncementList = ({
disableComment,
showLoadMore,
loadMore,
myName
myName,
}) => {
const listRef = useRef();
const [messages, setMessages] = useState(initialMessages);
@ -35,39 +34,44 @@ export const AnnouncementList = ({
setMessages(initialMessages);
}, [initialMessages]);
return (
<div
style={{
position: "relative",
position: 'relative',
flexGrow: 1,
width: "100%",
display: "flex",
flexDirection: "column",
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
overflow: 'auto'
overflow: 'auto',
}}
>
{messages.map((message) => {
const messageData = message?.tempData ? {
decryptedData: message?.tempData
} : announcementData[`${message.identifier}-${message.name}`];
const messageData = message?.tempData
? {
decryptedData: message?.tempData,
}
: announcementData[`${message.identifier}-${message.name}`];
return (
<div
key={message?.identifier}
style={{
marginBottom: "10px",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
marginBottom: '10px',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<AnnouncementItem myName={myName} disableComment={disableComment} setSelectedAnnouncement={setSelectedAnnouncement} message={message} messageData={messageData} />
<AnnouncementItem
myName={myName}
disableComment={disableComment}
setSelectedAnnouncement={setSelectedAnnouncement}
message={message}
messageData={messageData}
/>
</div>
);
})}
{/* <AutoSizer>
@ -83,14 +87,18 @@ export const AnnouncementList = ({
/>
)}
</AutoSizer> */}
<Box sx={{
<Box
sx={{
width: '100%',
marginTop: '25px',
display: 'flex',
justifyContent: 'center'
}}>
justifyContent: 'center',
}}
>
{showLoadMore && (
<CustomButton onClick={loadMore}>Load older announcements</CustomButton>
<CustomButton onClick={loadMore}>
Load older announcements
</CustomButton>
)}
</Box>
</div>

View File

@ -1,52 +1,78 @@
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import React, {
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
import { objectToBase64 } from '../../qdn/encryption/group-encryption'
import { ChatList } from './ChatList'
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import Tiptap from './TipTap'
import { CustomButton } from '../../App-styles'
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 { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getNameInfo } from '../Group/Group';
import { Spacer } from '../../common/Spacer';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getBaseApiReact, getBaseApiReactSocket, isMobile, pauseAllQueues, resumeAllQueues } from '../../App';
import {
getBaseApiReact,
getBaseApiReactSocket,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
import { getPublicKey } from '../../background';
import { useMessageQueue } from '../../MessageQueueContext';
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from '../../utils/events';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ShortUniqueId from "short-unique-id";
import ShortUniqueId from 'short-unique-id';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
import { MessageItem, ReplyPreview } from './MessageItem';
const uid = new ShortUniqueId({ length: 5 });
export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDirect, setNewChat, getTimestampEnterChat, myName, balance, close, setMobileViewModeKeepOpen}) => {
const { queueChats, addToQueue, processWithNewMessages} = useMessageQueue();
export const ChatDirect = ({
myAddress,
isNewChat,
selectedDirect,
setSelectedDirect,
setNewChat,
getTimestampEnterChat,
myName,
balance,
close,
setMobileViewModeKeepOpen,
}) => {
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [onEditMessage, setOnEditMessage] = useState(null)
const [onEditMessage, setOnEditMessage] = useState(null);
const [messages, setMessages] = useState([])
const [isSending, setIsSending] = useState(false)
const [directToValue, setDirectToValue] = useState('')
const hasInitialized = useRef(false)
const [isLoading, setIsLoading] = useState(false)
const [messages, setMessages] = useState([]);
const [isSending, setIsSending] = useState(false);
const [directToValue, setDirectToValue] = useState('');
const hasInitialized = useRef(false);
const [isLoading, setIsLoading] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState("")
const hasInitializedWebsocket = useRef(false)
const [chatReferences, setChatReferences] = useState({})
const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState('');
const hasInitializedWebsocket = useRef(false);
const [chatReferences, setChatReferences] = useState({});
const editorRef = useRef(null);
const socketRef = useRef(null);
const timeoutIdRef = useRef(null);
const [messageSize, setMessageSize] = useState(0)
const [messageSize, setMessageSize] = useState(0);
const groupSocketTimeoutRef = useRef(null);
const [replyMessage, setReplyMessage] = useState(null)
const [replyMessage, setReplyMessage] = useState(null);
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
@ -55,42 +81,47 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const publicKeyOfRecipientRef = useRef(null)
const getPublicKeyFunc = async (address)=> {
const publicKeyOfRecipientRef = useRef(null);
const getPublicKeyFunc = async (address) => {
try {
const publicKey = await getPublicKey(address)
if(publicKeyOfRecipientRef.current !== selectedDirect?.address) return
setPublicKeyOfRecipient(publicKey)
} catch (error) {
const publicKey = await getPublicKey(address);
if (publicKeyOfRecipientRef.current !== selectedDirect?.address) return;
setPublicKeyOfRecipient(publicKey);
} catch (error) {}
};
const tempMessages = useMemo(() => {
if (!selectedDirect?.address) return [];
if (queueChats[selectedDirect?.address]) {
return queueChats[selectedDirect?.address]?.filter(
(item) => !item?.chatReference
);
}
return [];
}, [selectedDirect?.address, queueChats]);
const tempChatReferences = useMemo(() => {
if (!selectedDirect?.address) return [];
if (queueChats[selectedDirect?.address]) {
return queueChats[selectedDirect?.address]?.filter(
(item) => !!item?.chatReference
);
}
return [];
}, [selectedDirect?.address, queueChats]);
const tempMessages = useMemo(()=> {
if(!selectedDirect?.address) return []
if(queueChats[selectedDirect?.address]){
return queueChats[selectedDirect?.address]?.filter((item)=> !item?.chatReference)
useEffect(() => {
if (selectedDirect?.address) {
publicKeyOfRecipientRef.current = selectedDirect?.address;
getPublicKeyFunc(publicKeyOfRecipientRef.current);
}
return []
}, [selectedDirect?.address, queueChats])
}, [selectedDirect?.address]);
const tempChatReferences = useMemo(()=> {
if(!selectedDirect?.address) return []
if(queueChats[selectedDirect?.address]){
return queueChats[selectedDirect?.address]?.filter((item)=> !!item?.chatReference)
}
return []
}, [selectedDirect?.address, queueChats])
useEffect(()=> {
if(selectedDirect?.address){
publicKeyOfRecipientRef.current = selectedDirect?.address
getPublicKeyFunc(publicKeyOfRecipientRef.current)
}
}, [selectedDirect?.address])
const middletierFunc = async (data: any, selectedDirectAddress: string, myAddress: string) => {
const middletierFunc = async (
data: any,
selectedDirectAddress: string,
myAddress: string
) => {
try {
if (hasInitialized.current) {
decryptMessages(data, true);
@ -99,9 +130,9 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
hasInitialized.current = true;
const url = `${getBaseApiReact()}/chat/messages?involving=${selectedDirectAddress}&involving=${myAddress}&encoding=BASE64&limit=0&reverse=false`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -109,22 +140,28 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
} catch (error) {
console.error(error);
}
}
};
const decryptMessages = (encryptedMessages: any[], isInitiated: boolean)=> {
const decryptMessages = (encryptedMessages: any[], isInitiated: boolean) => {
try {
return new Promise((res, rej)=> {
window.sendMessage("decryptDirect", {
return new Promise((res, rej) => {
window
.sendMessage('decryptDirect', {
data: encryptedMessages,
involvingAddress: selectedDirect?.address,
})
.then((decryptResponse) => {
if (!decryptResponse?.error) {
const response = processWithNewMessages(decryptResponse, selectedDirect?.address);
const response = processWithNewMessages(
decryptResponse,
selectedDirect?.address
);
res(response);
if (isInitiated) {
const formatted = response.filter((rawItem) => !rawItem?.chatReference).map((item) => ({
const formatted = response
.filter((rawItem) => !rawItem?.chatReference)
.map((item) => ({
...item,
id: item.signature,
text: item.message,
@ -134,21 +171,26 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
setChatReferences((prev) => {
const organizedChatReferences = { ...prev };
response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => {
response
.filter(
(rawItem) =>
!!rawItem?.chatReference && rawItem?.type === 'edit'
)
.forEach((item) => {
try {
organizedChatReferences[item.chatReference] = {
...(organizedChatReferences[item.chatReference] || {}),
edit: item
...(organizedChatReferences[item.chatReference] ||
{}),
edit: item,
};
} catch(error){
}
})
return organizedChatReferences
})
} catch (error) {}
});
return organizedChatReferences;
});
} else {
hasInitialized.current = true;
const formatted = response.filter((rawItem) => !rawItem?.chatReference)
const formatted = response
.filter((rawItem) => !rawItem?.chatReference)
.map((item) => ({
...item,
id: item.signature,
@ -160,32 +202,33 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
setChatReferences((prev) => {
const organizedChatReferences = { ...prev };
response.filter((rawItem) => !!rawItem?.chatReference && rawItem?.type === 'edit').forEach((item) => {
response
.filter(
(rawItem) =>
!!rawItem?.chatReference && rawItem?.type === 'edit'
)
.forEach((item) => {
try {
organizedChatReferences[item.chatReference] = {
...(organizedChatReferences[item.chatReference] || {}),
edit: item
...(organizedChatReferences[item.chatReference] ||
{}),
edit: item,
};
} catch(error){
}
})
return organizedChatReferences
})
} catch (error) {}
});
return organizedChatReferences;
});
}
return;
}
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
})
} catch (error) {
}
}
});
} catch (error) {}
};
const forceCloseWebSocket = () => {
if (socketRef.current) {
@ -212,7 +255,6 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
}
};
const initWebsocketMessageGroup = () => {
forceCloseWebSocket(); // Close any existing connection
@ -231,7 +273,11 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
clearTimeout(timeoutIdRef.current);
groupSocketTimeoutRef.current = setTimeout(pingWebSocket, 45000); // Ping every 45 seconds
} else {
middletierFunc(JSON.parse(e.data), selectedDirect?.address, myAddress)
middletierFunc(
JSON.parse(e.data),
selectedDirect?.address,
myAddress
);
setIsLoading(false);
}
@ -259,14 +305,14 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
};
};
const setDirectChatValueFunc = async (e)=> {
setDirectToValue(e.detail.directToValue)
}
const setDirectChatValueFunc = async (e) => {
setDirectToValue(e.detail.directToValue);
};
useEffect(() => {
subscribeToEvent("setDirectToValueNewChat", setDirectChatValueFunc);
subscribeToEvent('setDirectToValueNewChat', setDirectChatValueFunc);
return () => {
unsubscribeFromEvent("setDirectToValueNewChat", setDirectChatValueFunc);
unsubscribeFromEvent('setDirectToValueNewChat', setDirectChatValueFunc);
};
}, []);
@ -281,22 +327,30 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
};
}, [selectedDirect?.address, myAddress, isNewChat]);
const sendChatDirect = async ({ chatReference = undefined, messageText, otherData}: any, address, publicKeyOfRecipient, isNewChatVar)=> {
const sendChatDirect = async (
{ chatReference = undefined, messageText, otherData }: any,
address,
publicKeyOfRecipient,
isNewChatVar
) => {
try {
const directTo = isNewChatVar ? directToValue : address
const directTo = isNewChatVar ? directToValue : address;
if(!directTo) return
return new Promise((res, rej)=> {
window.sendMessage("sendChatDirect", {
if (!directTo) return;
return new Promise((res, rej) => {
window
.sendMessage(
'sendChatDirect',
{
directTo,
chatReference,
messageText,
otherData,
publicKeyOfRecipient,
address: directTo,
}, 120000)
},
120000
)
.then(async (response) => {
if (!response?.error) {
if (isNewChatVar) {
@ -304,7 +358,7 @@ const sendChatDirect = async ({ chatReference = undefined, messageText, otherDat
try {
getRecipientName = await getNameInfo(response.recipient);
} catch (error) {
console.error("Error fetching recipient name:", error);
console.error('Error fetching recipient name:', error);
}
setSelectedDirect({
address: response.recipient,
@ -315,11 +369,16 @@ const sendChatDirect = async ({ chatReference = undefined, messageText, otherDat
});
setNewChat(null);
window.sendMessage("addTimestampEnterChat", {
window
.sendMessage('addTimestampEnterChat', {
timestamp: Date.now(),
groupId: response.recipient,
}).catch((error) => {
console.error("Failed to add timestamp:", error.message || "An error occurred");
})
.catch((error) => {
console.error(
'Failed to add timestamp:',
error.message || 'An error occurred'
);
});
setTimeout(() => {
@ -332,32 +391,31 @@ const sendChatDirect = async ({ chatReference = undefined, messageText, otherDat
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
})
} catch (error) {
throw new Error(error)
throw new Error(error);
} finally {
}
}
const clearEditorContent = () => {
};
const clearEditorContent = () => {
if (editorRef.current) {
setMessageSize(0)
setMessageSize(0);
editorRef.current.chain().focus().clearContent().run();
if(isMobile){
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false)
executeEvent("sent-new-message-group", {})
setIsFocusedParent(false);
executeEvent('sent-new-message-group', {});
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
useEffect(() => {
};
useEffect(() => {
if (!editorRef?.current) return;
const handleUpdate = () => {
const htmlContent = editorRef?.current.getHTML();
@ -373,46 +431,48 @@ useEffect(() => {
return () => {
editorRef?.current.off('update', handleUpdate);
};
}, [editorRef?.current]);
}, [editorRef?.current]);
const sendMessage = async ()=> {
const sendMessage = async () => {
try {
if(messageSize > 4000) return
if (messageSize > 4000) return;
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
if(isSending) return
if (+balance < 4)
throw new Error('You need at least 4 QORT to send a message');
if (isSending) return;
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
setIsSending(true)
pauseAllQueues()
const message = JSON.stringify(htmlContent)
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return;
setIsSending(true);
pauseAllQueues();
const message = JSON.stringify(htmlContent);
if(isNewChat){
await sendChatDirect({ messageText: htmlContent}, null, null, true)
return
if (isNewChat) {
await sendChatDirect({ messageText: htmlContent }, null, null, true);
return;
}
let repliedTo = replyMessage?.signature
let repliedTo = replyMessage?.signature;
if (replyMessage?.chatReference) {
repliedTo = replyMessage?.chatReference
repliedTo = replyMessage?.chatReference;
}
let chatReference = onEditMessage?.signature
let chatReference = onEditMessage?.signature;
const otherData = {
...(onEditMessage?.decryptedData || {}),
specialId: uid.rnd(),
repliedTo: onEditMessage ? onEditMessage?.repliedTo : repliedTo,
type: chatReference ? 'edit' : ''
}
const sendMessageFunc = async () => {
return await sendChatDirect({ chatReference, messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false)
type: chatReference ? 'edit' : '',
};
const sendMessageFunc = async () => {
return await sendChatDirect(
{ chatReference, messageText: htmlContent, otherData },
selectedDirect?.address,
publicKeyOfRecipient,
false
);
};
// Add the function to the queue
const messageObj = {
@ -423,60 +483,71 @@ useEffect(() => {
...(otherData || {}),
text: htmlContent,
},
chatReference
}
addToQueue(sendMessageFunc, messageObj, 'chat-direct',
selectedDirect?.address );
chatReference,
};
addToQueue(
sendMessageFunc,
messageObj,
'chat-direct',
selectedDirect?.address
);
setTimeout(() => {
executeEvent("sent-new-message-group", {})
executeEvent('sent-new-message-group', {});
}, 150);
clearEditorContent()
setReplyMessage(null)
setOnEditMessage(null)
clearEditorContent();
setReplyMessage(null);
setOnEditMessage(null);
}
// send chat message
} catch (error) {
const errorMsg = error?.message || error
const errorMsg = error?.message || error;
setInfoSnack({
type: "error",
message: errorMsg === 'invalid signature' ? 'You need at least 4 QORT to send a message' : errorMsg,
type: 'error',
message:
errorMsg === 'invalid signature'
? 'You need at least 4 QORT to send a message'
: errorMsg,
});
setOpenSnack(true);
console.error(error)
console.error(error);
} finally {
setIsSending(false)
resumeAllQueues()
setIsSending(false);
resumeAllQueues();
}
};
const onReply = useCallback(
(message) => {
if (onEditMessage) {
clearEditorContent();
}
setReplyMessage(message);
setOnEditMessage(null);
editorRef?.current?.chain().focus();
},
[onEditMessage]
);
const onReply = useCallback((message)=> {
if(onEditMessage){
clearEditorContent()
}
setReplyMessage(message)
setOnEditMessage(null)
editorRef?.current?.chain().focus()
}, [onEditMessage])
const onEdit = useCallback((message)=> {
setOnEditMessage(message)
setReplyMessage(null)
const onEdit = useCallback((message) => {
setOnEditMessage(message);
setReplyMessage(null);
editorRef.current.chain().focus().setContent(message?.text).run();
}, [])
}, []);
return (
<div style={{
<div
style={{
height: isMobile ? '100%' : '100vh',
display: 'flex',
flexDirection: 'column',
width: '100%',
background: !isMobile && 'var(--bg-2)'
}}>
background: !isMobile && 'var(--bg-2)',
}}
>
{!isMobile && (
<Box onClick={close} sx={{
<Box
onClick={close}
sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
@ -486,47 +557,54 @@ useEffect(() => {
borderRadius: '3px',
background: 'rgb(35, 36, 40)',
margin: '10px 0px',
alignSelf: 'center'
}}>
<ArrowBackIcon sx={{
alignSelf: 'center',
}}
>
<ArrowBackIcon
sx={{
color: 'white',
fontSize: isMobile ? '20px' : '20px'
}}/>
<Typography sx={{
fontSize: isMobile ? '20px' : '20px',
}}
/>
<Typography
sx={{
color: 'white',
fontSize: isMobile ? '14px' : '14px'
}}>Close Direct Chat</Typography>
fontSize: isMobile ? '14px' : '14px',
}}
>
Close Direct Chat
</Typography>
</Box>
)}
{isMobile && (
<Box
sx={{
display: "flex",
alignItems: "center",
width: "100%",
marginTop: "14px",
justifyContent: "center",
height: "15px",
display: 'flex',
alignItems: 'center',
width: '100%',
marginTop: '14px',
justifyContent: 'center',
height: '15px',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "320px",
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '320px',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
width: "50px",
display: 'flex',
alignItems: 'center',
width: '50px',
}}
>
<ButtonBase
onClick={() => {
close()
close();
}}
>
<ReturnIcon />
@ -534,25 +612,28 @@ useEffect(() => {
</Box>
<Typography
sx={{
fontSize: "14px",
fontSize: '14px',
fontWeight: 600,
}}
>
{isNewChat ? '' : selectedDirect?.name || (selectedDirect?.address?.slice(0,10) + '...')}
{isNewChat
? ''
: selectedDirect?.name ||
selectedDirect?.address?.slice(0, 10) + '...'}
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
width: "50px",
justifyContent: "flex-end",
display: 'flex',
alignItems: 'center',
width: '50px',
justifyContent: 'flex-end',
}}
>
<ButtonBase
onClick={() => {
setSelectedDirect(null)
setMobileViewModeKeepOpen('')
setNewChat(false)
setSelectedDirect(null);
setMobileViewModeKeepOpen('');
setNewChat(false);
}}
>
<ExitIcon />
@ -564,21 +645,34 @@ useEffect(() => {
{isNewChat && (
<>
<Spacer height="30px" />
<Input sx={{
<Input
sx={{
fontSize: '18px',
padding: '5px'
}} placeholder='Name or address' value={directToValue} onChange={(e)=> setDirectToValue(e.target.value)} />
padding: '5px',
}}
placeholder="Name or address"
value={directToValue}
onChange={(e) => setDirectToValue(e.target.value)}
/>
</>
)}
<ChatList chatReferences={chatReferences} onEdit={onEdit} onReply={onReply} chatId={selectedDirect?.address} initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages} tempChatReferences={tempChatReferences}/>
<ChatList
chatReferences={chatReferences}
onEdit={onEdit}
onReply={onReply}
chatId={selectedDirect?.address}
initialMessages={messages}
myAddress={myAddress}
tempMessages={tempMessages}
tempChatReferences={tempChatReferences}
/>
<div style={{
<div
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
display: 'flex',
flexDirection: 'row',
@ -590,32 +684,36 @@ useEffect(() => {
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0
}}>
<div style={{
flexShrink: 0,
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
flexGrow: isMobile && 1,
overflow: !isMobile && "auto",
overflow: !isMobile && 'auto',
flexShrink: 0,
width: 'calc(100% - 100px)',
justifyContent: 'flex-end'
}}>
justifyContent: 'flex-end',
}}
>
{replyMessage && (
<Box sx={{
<Box
sx={{
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: 'calc(100% - 100px)',
justifyContent: 'flex-end'
}}>
justifyContent: 'flex-end',
}}
>
<ReplyPreview message={replyMessage} />
<ButtonBase
onClick={() => {
setReplyMessage(null)
setOnEditMessage(null)
setReplyMessage(null);
setOnEditMessage(null);
}}
>
<ExitIcon />
@ -623,22 +721,22 @@ useEffect(() => {
</Box>
)}
{onEditMessage && (
<Box sx={{
<Box
sx={{
display: 'flex',
gap: '5px',
alignItems: 'flex-start',
width: '100%'
}}>
width: '100%',
}}
>
<ReplyPreview isEdit message={onEditMessage} />
<ButtonBase
onClick={() => {
setReplyMessage(null)
setOnEditMessage(null)
clearEditorContent()
setReplyMessage(null);
setOnEditMessage(null);
clearEditorContent();
}}
>
<ExitIcon />
@ -646,24 +744,35 @@ useEffect(() => {
</Box>
)}
<Tiptap isFocusedParent={isFocusedParent} setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} setIsFocusedParent={setIsFocusedParent}/>
<Tiptap
isFocusedParent={isFocusedParent}
setEditorRef={setEditorRef}
onEnter={sendMessage}
isChat
disableEnter={isMobile ? true : false}
setIsFocusedParent={setIsFocusedParent}
/>
{messageSize > 750 && (
<Box sx={{
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'flex-start',
position: 'relative',
}}>
<Typography sx={{
}}
>
<Typography
sx={{
fontSize: '12px',
color: messageSize > 4000 ? 'var(--danger)' : 'unset'
}}>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`}</Typography>
color: messageSize > 4000 ? 'var(--danger)' : 'unset',
}}
>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`}</Typography>
</Box>
)}
</div>
<Box sx={{
<Box
sx={{
display: 'flex',
width: '100px',
@ -671,13 +780,12 @@ useEffect(() => {
justifyContent: 'center',
flexShrink: 0,
position: 'relative',
}}>
}}
>
<CustomButton
onClick={()=> {
if(isSending) return
sendMessage()
onClick={() => {
if (isSending) return;
sendMessage();
}}
style={{
marginTop: 'auto',
@ -687,7 +795,7 @@ useEffect(() => {
flexShrink: 0,
padding: '5px',
width: '100px',
minWidth: 'auto'
minWidth: 'auto',
}}
>
{isSending && (
@ -699,19 +807,26 @@ useEffect(() => {
left: '50%',
marginTop: '-12px',
marginLeft: '-12px',
color: 'white'
color: 'white',
}}
/>
)}
{` Send`}
</CustomButton>
</Box>
</div>
<LoadingSnackbar open={isLoading} info={{
message: "Loading chat... please wait."
}} />
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
<LoadingSnackbar
open={isLoading}
info={{
message: 'Loading chat... please wait.',
}}
/>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
</div>
)
}
);
};

File diff suppressed because it is too large Load Diff

View File

@ -4,30 +4,33 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
import { CreateCommonSecret } from "./CreateCommonSecret";
import { reusableGet } from "../../qdn/publish/pubish";
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption";
} 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 "../../App-styles";
import CircularProgress from "@mui/material/CircularProgress";
import { getBaseApi, getFee } from "../../background";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import ShortUniqueId from "short-unique-id";
import { AnnouncementList } from "./AnnouncementList";
} 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 CircularProgress from '@mui/material/CircularProgress';
import { getBaseApi, getFee } from '../../background';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { Box, Typography } 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 CampaignIcon from '@mui/icons-material/Campaign';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { AnnouncementDiscussion } from './AnnouncementDiscussion';
import {
MyContext,
getArbitraryEndpointReact,
@ -35,11 +38,11 @@ import {
isMobile,
pauseAllQueues,
resumeAllQueues,
} from "../../App";
import { RequestQueueWithPromise } from "../../utils/queue/queue";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { addDataPublishesFunc, getDataPublishesFunc } from "../Group/Group";
import { getRootHeight } from "../../utils/mobile/mobileUtils";
} from '../../App';
import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
import { getRootHeight } from '../../utils/mobile/mobileUtils';
export const requestQueueCommentCount = new RequestQueueWithPromise(3);
export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
@ -48,7 +51,8 @@ export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
export const saveTempPublish = async ({ data, key }: any) => {
return new Promise((res, rej) => {
window.sendMessage("saveTempPublish", {
window
.sendMessage('saveTempPublish', {
data,
key,
})
@ -60,15 +64,15 @@ export const saveTempPublish = async ({ data, key }: any) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
};
export const getTempPublish = async () => {
return new Promise((res, rej) => {
window.sendMessage("getTempPublish", {})
window
.sendMessage('getTempPublish', {})
.then((response) => {
if (!response?.error) {
res(response);
@ -77,16 +81,16 @@ export const getTempPublish = async () => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
};
export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
try {
return await new Promise((res, rej) => {
window.sendMessage("decryptSingleForPublishes", {
window
.sendMessage('decryptSingleForPublishes', {
data: encryptedMessages,
secretKeyObject: secretKey,
skipDecodeBase64: true,
@ -99,26 +103,23 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
};
export const handleUnencryptedPublishes = (publishes) => {
let publishesData = []
publishes.forEach((pub)=> {
let publishesData = [];
publishes.forEach((pub) => {
try {
const decryptToUnit8Array = base64ToUint8Array(pub);
const decodedData = uint8ArrayToObject(decryptToUnit8Array);
if(decodedData){
publishesData.push({decryptedData: decodedData})
if (decodedData) {
publishesData.push({ decryptedData: decodedData });
}
} catch (error) {
}
})
return publishesData
} catch (error) {}
});
return publishesData;
};
export const GroupAnnouncements = ({
selectedGroup,
@ -130,7 +131,7 @@ export const GroupAnnouncements = ({
isAdmin,
hide,
myName,
isPrivate
isPrivate,
}) => {
const [messages, setMessages] = useState([]);
const [isSending, setIsSending] = useState(false);
@ -159,12 +160,15 @@ export const GroupAnnouncements = ({
useEffect(() => {
if (!selectedGroup) return;
(async () => {
const res = await getDataPublishesFunc(selectedGroup, "anc");
const res = await getDataPublishesFunc(selectedGroup, 'anc');
dataPublishes.current = res || {};
})();
}, [selectedGroup]);
const getAnnouncementData = async ({ identifier, name, resource }, isPrivate) => {
const getAnnouncementData = async (
{ identifier, name, resource },
isPrivate
) => {
try {
let data = dataPublishes.current[`${name}-${identifier}`];
if (
@ -179,14 +183,17 @@ export const GroupAnnouncements = ({
});
if (!res?.ok) return;
data = await res.text();
await addDataPublishesFunc({ ...resource, data }, selectedGroup, "anc");
await addDataPublishesFunc({ ...resource, data }, selectedGroup, 'anc');
} else {
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];
if(!messageData) return
if (!messageData) return;
setAnnouncementData((prev) => {
return {
...prev,
@ -194,12 +201,17 @@ export const GroupAnnouncements = ({
};
});
} catch (error) {
console.error("error", error);
console.error('error', error);
}
};
useEffect(() => {
if ((!secretKey && isPrivate) || hasInitializedWebsocket.current || isPrivate === null) return;
if (
(!secretKey && isPrivate) ||
hasInitializedWebsocket.current ||
isPrivate === null
)
return;
setIsLoading(true);
// initWebsocketMessageGroup()
hasInitializedWebsocket.current = true;
@ -208,7 +220,8 @@ export const GroupAnnouncements = ({
const encryptChatMessage = async (data: string, secretKeyObject: any) => {
try {
return new Promise((res, rej) => {
window.sendMessage("encryptSingle", {
window
.sendMessage('encryptSingle', {
data,
secretKeyObject,
})
@ -220,16 +233,16 @@ export const GroupAnnouncements = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
} catch (error) {}
};
const publishAnc = async ({ encryptedData, identifier }: any) => {
return new Promise((res, rej) => {
window.sendMessage("publishGroupEncryptedResource", {
window
.sendMessage('publishGroupEncryptedResource', {
encryptedData,
identifier,
})
@ -241,9 +254,8 @@ export const GroupAnnouncements = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
};
const clearEditorContent = () => {
@ -266,7 +278,9 @@ export const GroupAnnouncements = ({
const getTempAnnouncements = await getTempPublish();
if (getTempAnnouncements?.announcement) {
let tempData = [];
Object.keys(getTempAnnouncements?.announcement || {}).filter((annKey)=> annKey?.startsWith(`grp-${selectedGroup}-anc`)).map((key) => {
Object.keys(getTempAnnouncements?.announcement || {})
.filter((annKey) => annKey?.startsWith(`grp-${selectedGroup}-anc`))
.map((key) => {
const value = getTempAnnouncements?.announcement[key];
tempData.push(value.data);
});
@ -278,27 +292,28 @@ export const GroupAnnouncements = ({
const publishAnnouncement = async () => {
try {
pauseAllQueues();
const fee = await getFee("ARBITRARY");
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to perform a ARBITRARY transaction?",
publishFee: fee.fee + " QORT",
message: 'Would you like to perform a ARBITRARY transaction?',
publishFee: fee.fee + ' QORT',
});
if (isSending) return;
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
if (!htmlContent?.trim() || htmlContent?.trim() === "<p></p>") return;
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return;
setIsSending(true);
const message = {
version: 1,
extra: {},
message: htmlContent,
};
const secretKeyObject = isPrivate === false ? null : await getSecretKey(false, true);
const secretKeyObject =
isPrivate === false ? null : await getSecretKey(false, true);
const message64: any = await objectToBase64(message);
const encryptSingle = isPrivate === false ? message64 : await encryptChatMessage(
message64,
secretKeyObject
);
const encryptSingle =
isPrivate === false
? message64
: await encryptChatMessage(message64, secretKeyObject);
const randomUid = uid.rnd();
const identifier = `grp-${selectedGroup}-anc-${randomUid}`;
const res = await publishAnc({
@ -309,13 +324,13 @@ export const GroupAnnouncements = ({
const dataToSaveToStorage = {
name: myName,
identifier,
service: "DOCUMENT",
service: 'DOCUMENT',
tempData: message,
created: Date.now(),
};
await saveTempPublish({
data: dataToSaveToStorage,
key: "announcement",
key: 'announcement',
});
setTempData(selectedGroup);
clearEditorContent();
@ -324,7 +339,7 @@ export const GroupAnnouncements = ({
} catch (error) {
if (!error) return;
setInfoSnack({
type: "error",
type: 'error',
message: error,
});
setOpenSnack(true);
@ -343,9 +358,9 @@ export const GroupAnnouncements = ({
const identifier = `grp-${selectedGroup}-anc-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -354,11 +369,14 @@ export const GroupAnnouncements = ({
setAnnouncements(responseData);
setIsLoading(false);
for (const data of responseData) {
getAnnouncementData({
getAnnouncementData(
{
name: data.name,
identifier: data.identifier,
resource: data,
}, isPrivate);
},
isPrivate
);
}
} catch (error) {
} finally {
@ -369,8 +387,13 @@ export const GroupAnnouncements = ({
);
React.useEffect(() => {
if(!secretKey && isPrivate) return
if (selectedGroup && !hasInitialized.current && !hide && isPrivate !== null) {
if (!secretKey && isPrivate) return;
if (
selectedGroup &&
!hasInitialized.current &&
!hide &&
isPrivate !== null
) {
getAnnouncements(selectedGroup, isPrivate);
hasInitialized.current = true;
}
@ -384,9 +407,9 @@ export const GroupAnnouncements = ({
const identifier = `grp-${selectedGroup}-anc-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -394,7 +417,10 @@ export const GroupAnnouncements = ({
setAnnouncements((prev) => [...prev, ...responseData]);
setIsLoading(false);
for (const data of responseData) {
getAnnouncementData({ name: data.name, identifier: data.identifier }, isPrivate);
getAnnouncementData(
{ name: data.name, identifier: data.identifier },
isPrivate
);
}
} catch (error) {}
};
@ -406,9 +432,9 @@ export const GroupAnnouncements = ({
const identifier = `grp-${selectedGroup}-anc-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -416,10 +442,13 @@ export const GroupAnnouncements = ({
if (!latestMessage) {
for (const data of responseData) {
try {
getAnnouncementData({
getAnnouncementData(
{
name: data.name,
identifier: data.identifier,
}, isPrivate);
},
isPrivate
);
} catch (error) {}
}
setAnnouncements(responseData);
@ -434,7 +463,10 @@ export const GroupAnnouncements = ({
for (const data of newArray) {
try {
getAnnouncementData({ name: data.name, identifier: data.identifier }, isPrivate);
getAnnouncementData(
{ name: data.name, identifier: data.identifier },
isPrivate
);
} catch (error) {}
}
setAnnouncements((prev) => [...newArray, ...prev]);
@ -486,13 +518,15 @@ export const GroupAnnouncements = ({
<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",
left: hide && "-1000px",
height: isMobile
? `calc(${rootHeight} - 127px`
: 'calc(100vh - 70px)',
display: 'flex',
flexDirection: 'column',
width: '100%',
visibility: hide && 'hidden',
position: hide && 'fixed',
left: hide && '-1000px',
}}
>
<AnnouncementDiscussion
@ -509,63 +543,62 @@ 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",
left: hide && "-1000px",
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
display: 'flex',
flexDirection: 'column',
width: '100%',
visibility: hide && 'hidden',
position: hide && 'fixed',
left: hide && '-1000px',
}}
>
<div
style={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
}}
>
{!isMobile && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
padding: isMobile ? "8px" : "25px",
fontSize: isMobile ? "16px" : "20px",
gap: "20px",
alignItems: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
padding: isMobile ? '8px' : '25px',
fontSize: isMobile ? '16px' : '20px',
gap: '20px',
alignItems: 'center',
}}
>
<CampaignIcon
sx={{
fontSize: isMobile ? "16px" : "30px",
fontSize: isMobile ? '16px' : '30px',
}}
/>
Group Announcements
</Box>
)}
<Spacer height={isMobile ? "0px" : "25px"} />
<Spacer height={isMobile ? '0px' : '25px'} />
</div>
{!isLoading && combinedListTempAndReal?.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<Typography
sx={{
fontSize: "16px",
fontSize: '16px',
}}
>
No announcements
@ -589,28 +622,28 @@ export const GroupAnnouncements = ({
style={{
// position: 'fixed',
// bottom: '0px',
backgroundColor: "#232428",
minHeight: isMobile ? "0px" : "150px",
maxHeight: isMobile ? "auto" : "400px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
width: "100%",
boxSizing: "border-box",
padding: isMobile ? "10px" : "20px",
position: isFocusedParent ? "fixed" : "relative",
bottom: isFocusedParent ? "0px" : "unset",
top: isFocusedParent ? "0px" : "unset",
zIndex: isFocusedParent ? 5 : "unset",
backgroundColor: '#232428',
minHeight: isMobile ? '0px' : '150px',
maxHeight: isMobile ? 'auto' : '400px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
boxSizing: 'border-box',
padding: isMobile ? '10px' : '20px',
position: isFocusedParent ? 'fixed' : 'relative',
bottom: isFocusedParent ? '0px' : 'unset',
top: isFocusedParent ? '0px' : 'unset',
zIndex: isFocusedParent ? 5 : 'unset',
flexShrink: 0,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
flexGrow: isMobile && 1,
overflow: "auto",
overflow: 'auto',
// height: '100%',
}}
>
@ -625,12 +658,12 @@ export const GroupAnnouncements = ({
</div>
<Box
sx={{
display: "flex",
width: "100&",
gap: "10px",
justifyContent: "center",
display: 'flex',
width: '100&',
gap: '10px',
justifyContent: 'center',
flexShrink: 0,
position: "relative",
position: 'relative',
}}
>
{isFocusedParent && (
@ -645,13 +678,13 @@ export const GroupAnnouncements = ({
// Unfocus the editor
}}
style={{
marginTop: "auto",
alignSelf: "center",
cursor: isSending ? "default" : "pointer",
background: "var(--danger)",
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: 'var(--danger)',
flexShrink: 0,
padding: isMobile && "5px",
fontSize: isMobile && "14px",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
}}
>
{` Close`}
@ -663,25 +696,25 @@ export const GroupAnnouncements = ({
publishAnnouncement();
}}
style={{
marginTop: "auto",
alignSelf: "center",
cursor: isSending ? "default" : "pointer",
background: isSending && "rgba(0, 0, 0, 0.8)",
marginTop: 'auto',
alignSelf: 'center',
cursor: isSending ? 'default' : 'pointer',
background: isSending && 'rgba(0, 0, 0, 0.8)',
flexShrink: 0,
padding: isMobile && "5px",
fontSize: isMobile && "14px",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
}}
>
{isSending && (
<CircularProgress
size={18}
sx={{
position: "absolute",
top: "50%",
left: "50%",
marginTop: "-12px",
marginLeft: "-12px",
color: "white",
position: 'absolute',
top: '50%',
left: '50%',
marginTop: '-12px',
marginLeft: '-12px',
color: 'white',
}}
/>
)}
@ -701,7 +734,7 @@ export const GroupAnnouncements = ({
<LoadingSnackbar
open={isLoading}
info={{
message: "Loading announcements... please wait.",
message: 'Loading announcements... please wait.',
}}
/>
</div>

View File

@ -1,25 +1,28 @@
import React, { useEffect, useState } from 'react';
import syncedImg from '../assets/syncStatus/synced.png'
import syncedMintingImg from '../assets/syncStatus/synced_minting.png'
import syncingImg from '../assets/syncStatus/syncing.png'
import { useEffect, useState } from 'react';
import syncedImg from '../assets/syncStatus/synced.png';
import syncedMintingImg from '../assets/syncStatus/synced_minting.png';
import syncingImg from '../assets/syncStatus/syncing.png';
import { getBaseApiReact } from '../App';
import './CoreSyncStatus.css'
export const CoreSyncStatus = ({imageSize, position}) => {
import '../styles/CoreSyncStatus.css';
import { useTheme } from '@mui/material';
export const CoreSyncStatus = () => {
const [nodeInfos, setNodeInfos] = useState({});
const [coreInfos, setCoreInfos] = useState({});
const [isUsingGateway, setIsUsingGateway] = useState(false);
const theme = useTheme();
useEffect(() => {
const getNodeInfos = async () => {
try {
setIsUsingGateway(!!getBaseApiReact()?.includes('ext-node.qortal.link'))
setIsUsingGateway(
!!getBaseApiReact()?.includes('ext-node.qortal.link')
);
const url = `${getBaseApiReact()}/admin/status`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const data = await response.json();
@ -30,14 +33,12 @@ export const CoreSyncStatus = ({imageSize, position}) => {
};
const getCoreInfos = async () => {
try {
const url = `${getBaseApiReact()}/admin/info`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const data = await response.json();
@ -59,55 +60,85 @@ export const CoreSyncStatus = ({imageSize, position}) => {
}, []);
const renderSyncStatusIcon = () => {
const { isSynchronizing = false, syncPercent = 0, isMintingPossible = false, height = 0, numberOfConnections = 0 } = nodeInfos;
const buildVersion = coreInfos?.buildVersion ? coreInfos?.buildVersion.substring(0, 12) : '';
const {
isSynchronizing = false,
syncPercent = 0,
isMintingPossible = false,
height = 0,
numberOfConnections = 0,
} = nodeInfos;
const buildVersion = coreInfos?.buildVersion
? coreInfos?.buildVersion.substring(0, 12)
: '';
let imagePath = syncingImg;
let message = `Synchronizing`
let message = `Synchronizing`;
if (isMintingPossible && !isUsingGateway) {
imagePath = syncedMintingImg;
message = `${isSynchronizing ? 'Synchronizing' : 'Synchronized'} ${'(Minting)'}`
message = `${isSynchronizing ? 'Synchronizing' : 'Synchronized'} ${'(Minting)'}`;
} else if (isSynchronizing === true && syncPercent === 99) {
imagePath = syncingImg
imagePath = syncingImg;
} else if (isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncingImg;
message = `Synchronizing ${isUsingGateway ? '' :'(Not Minting)'}`
message = `Synchronizing ${isUsingGateway ? '' : '(Not Minting)'}`;
} else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncedImg
message = `Synchronized ${isUsingGateway ? '' :'(Not Minting)'}`
imagePath = syncedImg;
message = `Synchronized ${isUsingGateway ? '' : '(Not Minting)'}`;
} else if (isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncingImg;
message = `Synchronizing ${isUsingGateway ? '' :'(Minting)'}`
message = `Synchronizing ${isUsingGateway ? '' : '(Minting)'}`;
} else if (!isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncedMintingImg;
message = `Synchronized ${isUsingGateway ? '' :'(Minting)'}`
message = `Synchronized ${isUsingGateway ? '' : '(Minting)'}`;
}
return (
<div className="tooltip" style={{ display: 'inline' }}>
<span><img src={imagePath} style={{ height: 'auto', width: imageSize ? imageSize : '24px' }} alt="sync status" /></span>
<div className="bottom" style={{
right: position && 'unset',
left: position && '0px'
}}>
<div
className="tooltip"
data-theme={theme.palette.mode}
style={{ display: 'inline' }}
>
<span>
<img
src={imagePath}
style={{ height: 'auto', width: '24px' }}
alt="sync status"
/>
</span>
<div
className="bottom"
style={{
right: 'unset',
left: '0px',
}}
>
<h3>Core Information</h3>
<h4 className="lineHeight">Core Version: <span style={{ color: '#03a9f4' }}>{buildVersion}</span></h4>
<h4 className="lineHeight">
Core Version:{' '}
<span style={{ color: '#03a9f4' }}>{buildVersion}</span>
</h4>
<h4 className="lineHeight">{message}</h4>
<h4 className="lineHeight">Block Height: <span style={{ color: '#03a9f4' }}>{height || ''}</span></h4>
<h4 className="lineHeight">Connected Peers: <span style={{ color: '#03a9f4' }}>{numberOfConnections || ''}</span></h4>
<h4 className="lineHeight">Using public node: <span style={{ color: '#03a9f4' }}>{isUsingGateway?.toString()}</span></h4>
<h4 className="lineHeight">
Block Height:{' '}
<span style={{ color: '#03a9f4' }}>{height || ''}</span>
</h4>
<h4 className="lineHeight">
Connected Peers:{' '}
<span style={{ color: '#03a9f4' }}>
{numberOfConnections || ''}
</span>
</h4>
<h4 className="lineHeight">
Using public node:{' '}
<span style={{ color: '#03a9f4' }}>
{isUsingGateway?.toString()}
</span>
</h4>
<i></i>
</div>
</div>
);
};
return (
<div id="core-sync-status-id">
{renderSyncStatusIcon()}
</div>
);
return <div id="core-sync-status-id">{renderSyncStatusIcon()}</div>;
};

View File

@ -1,13 +1,13 @@
import { ButtonBase, Typography, useTheme } from "@mui/material";
import Box from "@mui/material/Box";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
import AppIcon from "../../assets/svgs/AppIcon.svg";
import { ButtonBase, Typography, useTheme } from '@mui/material';
import Box from '@mui/material/Box';
import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import AppIcon from '../../assets/svgs/AppIcon.svg';
import { HomeIcon } from "../../assets/Icons/HomeIcon";
import { Save } from "../Save/Save";
import { useRecoilState } from "recoil";
import { enabledDevModeAtom } from "../../atoms/global";
import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { Save } from '../Save/Save';
import { useRecoilState } from 'recoil';
import { enabledDevModeAtom } from '../../atoms/global';
export const IconWrapper = ({
children,
@ -22,25 +22,25 @@ 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%",
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",
: 'transparent',
color: color ? color : theme.palette.text.primary,
}}
>
{children}
<Typography
sx={{
fontFamily: "Inter",
fontSize: "12px",
fontFamily: 'Inter',
fontSize: '12px',
fontWeight: 500,
color: theme.palette.text.primary,
}}
@ -72,20 +72,20 @@ export const DesktopFooter = ({
return (
<Box
sx={{
width: "100%",
position: "absolute",
width: '100%',
position: 'absolute',
bottom: 0,
display: "flex",
alignItems: "center",
height: "100px", // Footer height
display: 'flex',
alignItems: 'center',
height: '100px', // Footer height
zIndex: 1,
justifyContent: "center",
justifyContent: 'center',
}}
>
<Box
sx={{
display: "flex",
gap: "20px",
display: 'flex',
gap: '20px',
}}
>
<ButtonBase
@ -94,15 +94,13 @@ export const DesktopFooter = ({
}}
>
<IconWrapper label="Home" selected={isHome}>
<HomeIcon
height={30}
/>
<HomeIcon height={30} />
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode("apps");
setDesktopViewMode('apps');
setIsOpenSideViewDirects(false);
setIsOpenSideViewGroups(false);
}}
@ -114,7 +112,7 @@ export const DesktopFooter = ({
<ButtonBase
onClick={() => {
setDesktopSideView("groups");
setDesktopSideView('groups');
}}
>
<IconWrapper label="Groups" selected={isGroups}>
@ -122,10 +120,10 @@ export const DesktopFooter = ({
height={30}
color={
hasUnreadGroups
? "var(--danger)"
? 'var(--danger)'
: isGroups
? "white"
: "rgba(250, 250, 250, 0.5)"
? 'white'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>
@ -133,7 +131,7 @@ export const DesktopFooter = ({
<ButtonBase
onClick={() => {
setDesktopSideView("directs");
setDesktopSideView('directs');
}}
>
<IconWrapper label="Messaging" selected={isDirects}>
@ -141,10 +139,10 @@ export const DesktopFooter = ({
height={30}
color={
hasUnreadDirects
? "var(--danger)"
? 'var(--danger)'
: isDirects
? "white"
: "rgba(250, 250, 250, 0.5)"
? 'white'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>
@ -154,7 +152,7 @@ export const DesktopFooter = ({
{isEnabledDevMode && (
<ButtonBase
onClick={() => {
setDesktopViewMode("dev");
setDesktopViewMode('dev');
setIsOpenSideViewDirects(false);
setIsOpenSideViewGroups(false);
}}

View File

@ -1,47 +1,55 @@
import * as React from "react";
import * as React from 'react';
import {
BottomNavigation,
BottomNavigationAction,
ButtonBase,
Typography,
} from "@mui/material";
import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
import Box from "@mui/material/Box";
import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
import { CustomSvg } from "../../common/CustomSvg";
import { WalletIcon } from "../../assets/Icons/WalletIcon";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
import { TradingIcon } from "../../assets/Icons/TradingIcon";
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
import { HomeIcon } from "../../assets/Icons/HomeIcon";
import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2";
import { ChatIcon } from "../../assets/Icons/ChatIcon";
import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
import { MembersIcon } from "../../assets/Icons/MembersIcon";
import { AdminsIcon } from "../../assets/Icons/AdminsIcon";
} from '@mui/material';
import { Home, Groups, Message, ShowChart } from '@mui/icons-material';
import Box from '@mui/material/Box';
import BottomLogo from '../../assets/svgs/BottomLogo5.svg';
import { CustomSvg } from '../../common/CustomSvg';
import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { TradingIcon } from '../../assets/Icons/TradingIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2';
import { ChatIcon } from '../../assets/Icons/ChatIcon';
import { ThreadsIcon } from '../../assets/Icons/ThreadsIcon';
import { MembersIcon } from '../../assets/Icons/MembersIcon';
import { AdminsIcon } from '../../assets/Icons/AdminsIcon';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
const IconWrapper = ({ children, label, color, selected, selectColor, customHeight }) => {
const IconWrapper = ({
children,
label,
color,
selected,
selectColor,
customHeight,
}) => {
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "5px",
flexDirection: "column",
height: customHeight ? customHeight : "65px",
width: customHeight ? customHeight : "65px",
borderRadius: "50%",
backgroundColor: selected ? selectColor || "rgba(28, 29, 32, 1)" : "transparent",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '5px',
flexDirection: 'column',
height: customHeight ? customHeight : '65px',
width: customHeight ? customHeight : '65px',
borderRadius: '50%',
backgroundColor: selected
? selectColor || 'rgba(28, 29, 32, 1)'
: 'transparent',
}}
>
{children}
<Typography
sx={{
fontFamily: "Inter",
fontSize: "10px",
fontFamily: 'Inter',
fontSize: '10px',
fontWeight: 500,
color: color,
}}
@ -83,60 +91,67 @@ export const DesktopHeader = ({
isChat,
isForum,
setGroupSection,
isPrivate
isPrivate,
}) => {
const [value, setValue] = React.useState(0);
return (
<Box
sx={{
width: "100%",
display: "flex",
alignItems: "center",
height: "70px", // Footer height
width: '100%',
display: 'flex',
alignItems: 'center',
height: '70px', // Footer height
zIndex: 1,
justifyContent: "space-between",
padding: "10px",
justifyContent: 'space-between',
padding: '10px',
}}
>
<Box sx={{
<Box
sx={{
display: 'flex',
gap: '10px'
}}>
gap: '10px',
}}
>
{isPrivate && (
<LockIcon sx={{
color: 'var(--green)'
}} />
<LockIcon
sx={{
color: 'var(--green)',
}}
/>
)}
{isPrivate === false && (
<NoEncryptionGmailerrorredIcon sx={{
color: 'var(--danger)'
}} />
<NoEncryptionGmailerrorredIcon
sx={{
color: 'var(--danger)',
}}
/>
)}
<Typography
sx={{
fontSize: "16px",
fontSize: '16px',
fontWeight: 600,
}}
>
{selectedGroup?.groupId === '0' ? 'General' :selectedGroup?.groupName}
{selectedGroup?.groupId === '0'
? 'General'
: selectedGroup?.groupName}
</Typography>
</Box>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile'
display: 'flex',
gap: '20px',
alignItems: 'center',
visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile',
}}
>
<ButtonBase
onClick={() => {
goToAnnouncements()
goToAnnouncements();
}}
>
<IconWrapper
color={isAnnouncement ? "black" :"rgba(250, 250, 250, 0.5)"}
color={isAnnouncement ? 'black' : 'rgba(250, 250, 250, 0.5)'}
label="ANN"
selected={isAnnouncement}
selectColor="#09b6e8"
@ -147,10 +162,10 @@ export const DesktopHeader = ({
width={20}
color={
isUnread
? "var(--unread)"
? 'var(--unread)'
: isAnnouncement
? "black"
: "rgba(250, 250, 250, 0.5)"
? 'black'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>
@ -158,11 +173,11 @@ export const DesktopHeader = ({
<ButtonBase
onClick={() => {
goToChat()
goToChat();
}}
>
<IconWrapper
color={isChat ? "black" :"rgba(250, 250, 250, 0.5)"}
color={isChat ? 'black' : 'rgba(250, 250, 250, 0.5)'}
label="Chat"
selected={isChat}
selectColor="#09b6e8"
@ -173,10 +188,10 @@ export const DesktopHeader = ({
width={20}
color={
isUnreadChat
? "var(--unread)"
? 'var(--unread)'
: isChat
? "black"
: "rgba(250, 250, 250, 0.5)"
? 'black'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>
@ -184,12 +199,11 @@ export const DesktopHeader = ({
<ButtonBase
onClick={() => {
setGroupSection("forum");
setGroupSection('forum');
}}
>
<IconWrapper
color={isForum ? 'black' : "rgba(250, 250, 250, 0.5)"}
color={isForum ? 'black' : 'rgba(250, 250, 250, 0.5)'}
label="Threads"
selected={isForum}
selectColor="#09b6e8"
@ -198,18 +212,13 @@ export const DesktopHeader = ({
<ThreadsIcon
height={25}
width={20}
color={
isForum
? "black"
: "rgba(250, 250, 250, 0.5)"
}
color={isForum ? 'black' : 'rgba(250, 250, 250, 0.5)'}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setOpenManageMembers(true)
setOpenManageMembers(true);
}}
>
<IconWrapper
@ -221,22 +230,21 @@ export const DesktopHeader = ({
<MembersIcon
height={25}
width={20}
color={
isForum
? "white"
: "rgba(250, 250, 250, 0.5)"
}
color={isForum ? 'white' : 'rgba(250, 250, 250, 0.5)'}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setGroupSection("adminSpace");
setGroupSection('adminSpace');
}}
>
<IconWrapper
color={groupSection === 'adminSpace' ? 'black' : "rgba(250, 250, 250, 0.5)"}
color={
groupSection === 'adminSpace'
? 'black'
: 'rgba(250, 250, 250, 0.5)'
}
label="Admins"
selected={groupSection === 'adminSpace'}
customHeight="55px"
@ -247,8 +255,8 @@ export const DesktopHeader = ({
width={20}
color={
groupSection === 'adminSpace'
? "black"
: "rgba(250, 250, 250, 0.5)"
? 'black'
: 'rgba(250, 250, 250, 0.5)'
}
/>
</IconWrapper>

View File

@ -1,12 +1,12 @@
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 ThemeSelector from "./Theme/ThemeSelector";
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 ThemeSelector from './Theme/ThemeSelector';
export const DesktopSideBar = ({
goToHome,
@ -30,19 +30,19 @@ export const DesktopSideBar = ({
return (
<Box
sx={{
width: "60px",
flexDirection: "column",
height: "100vh",
alignItems: "center",
display: "flex",
gap: "25px",
width: '60px',
flexDirection: 'column',
height: '100vh',
alignItems: 'center',
display: 'flex',
gap: '25px',
}}
>
<ButtonBase
sx={{
width: "60px",
height: "60px",
paddingTop: "23px",
width: '60px',
height: '60px',
paddingTop: '23px',
}}
onClick={() => {
goToHome();
@ -51,38 +51,38 @@ export const DesktopSideBar = ({
<HomeIcon
height={34}
color={
desktopViewMode === "home" ? "white" : "rgba(250, 250, 250, 0.5)"
desktopViewMode === 'home' ? 'white' : 'rgba(250, 250, 250, 0.5)'
}
/>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode("apps");
setDesktopViewMode('apps');
// setIsOpenSideViewDirects(false)
// setIsOpenSideViewGroups(false)
}}
>
<IconWrapper
color={isApps ? "white" : "rgba(250, 250, 250, 0.5)"}
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
label="Apps"
selected={isApps}
disableWidth
>
<AppsIcon
color={isApps ? "white" : "rgba(250, 250, 250, 0.5)"}
color={isApps ? 'white' : 'rgba(250, 250, 250, 0.5)'}
height={30}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode("chat");
setDesktopViewMode('chat');
}}
>
<IconWrapper
color={
hasUnreadDirects || hasUnreadGroups
? "var(--unread)"
? 'var(--unread)'
: theme.palette.text.primary
}
label="Chat"
@ -92,7 +92,7 @@ export const DesktopSideBar = ({
height={30}
color={
hasUnreadDirects || hasUnreadGroups
? "var(--unread)"
? 'var(--unread)'
: theme.palette.text.primary
}
/>
@ -121,24 +121,22 @@ export const DesktopSideBar = ({
{isEnabledDevMode && (
<ButtonBase
onClick={() => {
setDesktopViewMode("dev");
setDesktopViewMode('dev');
}}
>
<IconWrapper
color={
desktopViewMode === "dev" ? "white" : "rgba(250, 250, 250, 0.5)"
desktopViewMode === 'dev' ? 'white' : 'rgba(250, 250, 250, 0.5)'
}
label="Dev"
disableWidth
>
<AppsIcon
height={30}
/>
<AppsIcon height={30} />
</IconWrapper>
</ButtonBase>
)}
<ThemeSelector style={{ position: "fixed", bottom: "1%" }} />
<ThemeSelector style={{ position: 'fixed', bottom: '1%' }} />
</Box>
);
};

View File

@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useState } from 'react';
import {
Box,
ButtonBase,
@ -8,56 +7,70 @@ import {
Popover,
Tooltip,
Typography,
} from "@mui/material";
import NotificationsIcon from "@mui/icons-material/Notifications";
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
import { formatDate } from "../utils/time";
import { useHandlePaymentNotification } from "../hooks/useHandlePaymentNotification";
import { executeEvent } from "../utils/events";
useTheme,
} from '@mui/material';
import NotificationsIcon from '@mui/icons-material/Notifications';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import { formatDate } from '../utils/time';
import { useHandlePaymentNotification } from '../hooks/useHandlePaymentNotification';
import { executeEvent } from '../utils/events';
export const GeneralNotifications = ({ address }) => {
const [anchorEl, setAnchorEl] = useState(null);
const {latestTx,
const {
latestTx,
getNameOrAddressOfSenderMiddle,
hasNewPayment, setLastEnteredTimestampPayment, nameAddressOfSender} = useHandlePaymentNotification(address)
hasNewPayment,
setLastEnteredTimestampPayment,
nameAddressOfSender,
} = useHandlePaymentNotification(address);
const handlePopupClick = (event) => {
event.stopPropagation(); // Prevent parent onClick from firing
setAnchorEl(event.currentTarget);
};
const theme = useTheme();
return (
<>
<ButtonBase
onClick={(e) => {
handlePopupClick(e);
}}
style={{}}
>
<Tooltip
title={<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>PAYMENT NOTIFICATION</span>}
title={
<span style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}>
PAYMENT NOTIFICATION
</span>
}
placement="left"
arrow
sx={{ fontSize: "24" }}
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
color: '#ffffff',
backgroundColor: '#444444',
},
},
arrow: {
sx: {
color: "#444444",
color: '#444444',
},
},
}}
>
<NotificationsIcon
sx={{
color: hasNewPayment ? "var(--unread)" : "rgba(255, 255, 255, 0.5)",
color: hasNewPayment
? 'var(--unread)'
: theme.palette.mode === 'dark'
? 'rgb(209, 209, 209)'
: 'rgba(41, 41, 43, 1)',
}}
/>
</Tooltip>
@ -67,81 +80,91 @@ export const GeneralNotifications = ({ address }) => {
open={!!anchorEl}
anchorEl={anchorEl}
onClose={() => {
if(hasNewPayment){
setLastEnteredTimestampPayment(Date.now())
if (hasNewPayment) {
setLastEnteredTimestampPayment(Date.now());
}
setAnchorEl(null)
setAnchorEl(null);
}} // Close popover on click outside
>
<Box
sx={{
width: "300px",
maxWidth: "100%",
maxHeight: "60vh",
overflow: "auto",
padding: "5px",
display: "flex",
flexDirection: "column",
alignItems: hasNewPayment ? "flex-start" : "center",
width: '300px',
maxWidth: '100%',
maxHeight: '60vh',
overflow: 'auto',
padding: '5px',
display: 'flex',
flexDirection: 'column',
alignItems: hasNewPayment ? 'flex-start' : 'center',
}}
>
{!hasNewPayment && <Typography sx={{
userSelect: 'none'
}}>No new notifications</Typography>}
{!hasNewPayment && (
<Typography
sx={{
userSelect: 'none',
}}
>
No new notifications
</Typography>
)}
{hasNewPayment && (
<MenuItem
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
width: "100%",
alignItems: "flex-start",
textWrap: "auto",
display: 'flex',
flexDirection: 'column',
gap: '5px',
width: '100%',
alignItems: 'flex-start',
textWrap: 'auto',
}}
onClick={() => {
setAnchorEl(null)
executeEvent('openWalletsApp', {})
setAnchorEl(null);
executeEvent('openWalletsApp', {});
}}
>
<Card sx={{
<Card
sx={{
padding: '10px',
width: '100%',
backgroundColor: "#1F2023",
backgroundColor: '#1F2023',
gap: '5px',
display: 'flex',
flexDirection: 'column'
}}>
flexDirection: 'column',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
justifyContent: "space-between",
display: 'flex',
alignItems: 'center',
gap: '5px',
justifyContent: 'space-between',
}}
>
<AccountBalanceWalletIcon
sx={{
color: "white",
color: 'white',
}}
/>{" "}
/>{' '}
{formatDate(latestTx?.timestamp)}
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
justifyContent: "space-between",
display: 'flex',
alignItems: 'center',
gap: '5px',
justifyContent: 'space-between',
}}
>
<Typography>{latestTx?.amount}</Typography>
</Box>
<Typography sx={{
fontSize: '0.8rem'
}}>{nameAddressOfSender.current[latestTx?.creatorAddress] || getNameOrAddressOfSenderMiddle(latestTx?.creatorAddress)}</Typography>
<Typography
sx={{
fontSize: '0.8rem',
}}
>
{nameAddressOfSender.current[latestTx?.creatorAddress] ||
getNameOrAddressOfSenderMiddle(latestTx?.creatorAddress)}
</Typography>
</Card>
</MenuItem>
)}

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import {
Box,
Button,
@ -9,12 +9,12 @@ import {
DialogActions,
DialogContent,
Typography,
} from "@mui/material";
import { CustomButton, CustomButtonAccept } from "../../App-styles";
import { getBaseApiReact, MyContext } from "../../App";
import { getFee } from "../../background";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { FidgetSpinner } from "react-loader-spinner";
} from '@mui/material';
import { CustomButton, CustomButtonAccept } from '../../styles/App-styles';
import { getBaseApiReact, MyContext } from '../../App';
import { getFee } from '../../background';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { FidgetSpinner } from 'react-loader-spinner';
export const JoinGroup = ({ memberGroups }) => {
const { show, setTxList } = useContext(MyContext);
@ -42,43 +42,45 @@ export const JoinGroup = ({ memberGroups }) => {
};
useEffect(() => {
subscribeToEvent("globalActionJoinGroup", handleJoinGroup);
subscribeToEvent('globalActionJoinGroup', handleJoinGroup);
return () => {
unsubscribeFromEvent("globalActionJoinGroup", handleJoinGroup);
unsubscribeFromEvent('globalActionJoinGroup', handleJoinGroup);
};
}, []);
const isInGroup = useMemo(()=> {
return !!memberGroups.find((item)=> +item?.groupId === +groupInfo?.groupId)
}, [memberGroups, groupInfo])
const isInGroup = useMemo(() => {
return !!memberGroups.find(
(item) => +item?.groupId === +groupInfo?.groupId
);
}, [memberGroups, groupInfo]);
const joinGroup = 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,
@ -90,7 +92,7 @@ export const JoinGroup = ({ memberGroups }) => {
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,
@ -105,7 +107,7 @@ export const JoinGroup = ({ memberGroups }) => {
return;
} else {
setInfoSnack({
type: "error",
type: 'error',
message: response?.error,
});
setOpenSnack(true);
@ -114,8 +116,8 @@ export const JoinGroup = ({ memberGroups }) => {
})
.catch((error) => {
setInfoSnack({
type: "error",
message: error.message || "An error occurred",
type: 'error',
message: error.message || 'An error occurred',
});
setOpenSnack(true);
rej(error);
@ -138,37 +140,37 @@ export const JoinGroup = ({ memberGroups }) => {
{!groupInfo && (
<Box
sx={{
width: "325px",
height: "150px",
display: "flex",
alignItems: "center",
justifyContent: "center",
width: '325px',
height: '150px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{" "}
{' '}
<CircularProgress
size={25}
sx={{
color: "white",
color: 'white',
}}
/>{" "}
/>{' '}
</Box>
)}
<Box
sx={{
width: "325px",
height: "auto",
maxHeight: "400px",
display: !groupInfo ? "none" : "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: 'auto',
maxHeight: '400px',
display: !groupInfo ? 'none' : 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<Typography
sx={{
fontSize: "15px",
fontSize: '15px',
fontWeight: 600,
}}
>
@ -176,7 +178,7 @@ export const JoinGroup = ({ memberGroups }) => {
</Typography>
<Typography
sx={{
fontSize: "15px",
fontSize: '15px',
fontWeight: 600,
}}
>
@ -185,7 +187,7 @@ export const JoinGroup = ({ memberGroups }) => {
{groupInfo?.description && (
<Typography
sx={{
fontSize: "15px",
fontSize: '15px',
fontWeight: 600,
}}
>
@ -195,7 +197,7 @@ export const JoinGroup = ({ memberGroups }) => {
{isInGroup && (
<Typography
sx={{
fontSize: "14px",
fontSize: '14px',
fontWeight: 600,
}}
>
@ -205,7 +207,7 @@ export const JoinGroup = ({ memberGroups }) => {
{!isInGroup && groupInfo?.isOpen === false && (
<Typography
sx={{
fontSize: "14px",
fontSize: '14px',
fontWeight: 600,
}}
>
@ -216,21 +218,23 @@ export const JoinGroup = ({ memberGroups }) => {
</Box>
</DialogContent>
<DialogActions>
<ButtonBase onClick={() => {
<ButtonBase
onClick={() => {
joinGroup(groupInfo, groupInfo?.isOpen);
setIsOpen(false);
}} disabled={isInGroup}>
}}
disabled={isInGroup}
>
<CustomButtonAccept
color="black"
bgColor="var(--green)"
sx={{
minWidth: "102px",
height: "45px",
minWidth: '102px',
height: '45px',
fontSize: '16px',
opacity: isInGroup ? 0.1 : 1
opacity: isInGroup ? 0.1 : 1,
}}
>
Join
</CustomButtonAccept>
@ -240,8 +244,8 @@ export const JoinGroup = ({ memberGroups }) => {
color="black"
bgColor="var(--danger)"
sx={{
minWidth: "102px",
height: "45px",
minWidth: '102px',
height: '45px',
}}
onClick={() => setIsOpen(false)}
>
@ -259,14 +263,14 @@ export const JoinGroup = ({ memberGroups }) => {
{isLoadingJoinGroup && (
<Box
sx={{
position: "absolute",
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<FidgetSpinner

View File

@ -10,15 +10,23 @@ import {
Typography,
} from "@mui/material";
import React, { useContext, useEffect, useState } from "react";
import { MyContext } from "../../App";
import { getBaseApiReact, MyContext } from "../../App";
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
export const BlockedUsersModal = ({ close }) => {
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 [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState("");
const { getAllBlockedUsers, removeBlockFromList, addToBlockList } = useContext(MyContext);
const [addressesWithNames, setAddressesWithNames] = useState({})
const { isShow, onCancel, onOk, show, message } = useModal();
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
useContext(MyContext);
const [blockedUsers, setBlockedUsers] = useState({
addresses: {},
names: {},
@ -28,20 +36,119 @@ export const BlockedUsersModal = ({ close }) => {
};
useEffect(() => {
if(!isOpenBlockedModal) return
fetchBlockedUsers();
}, [isOpenBlockedModal]);
const getNames = async () => {
// const validApi = await findUsableApi();
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
}
return true;
});
await Promise.all(getMemNames);
setAddressesWithNames(addressNames)
};
const blockUser = async (e, user?: string) => {
try {
const valUser = user || value
if (!valUser) return;
const isAddress = validateAddress(valUser);
let userName = null;
let userAddress = null;
if (isAddress) {
userAddress = valUser;
const name = await getNameInfo(valUser);
if (name) {
userName = name;
}
}
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) {
userAddress = data.owner;
userName = valUser;
}
}
if(!userName){
await addToBlockList(userAddress, null);
fetchBlockedUsers();
setHasChanged(true);
executeEvent('updateChatMessagesWithBlocks', true)
setValue('')
return
}
const responseModal = await show({
userName,
userAddress,
});
if (responseModal === "both") {
await addToBlockList(userAddress, userName);
} else if (responseModal === "address") {
await addToBlockList(userAddress, null);
} else if (responseModal === "name") {
await addToBlockList(null, userName);
}
fetchBlockedUsers();
setHasChanged(true);
setValue('')
if(user){
setIsOpenBlockedModal(false)
}
if(responseModal === 'both' || responseModal === 'address'){
executeEvent('updateChatMessagesWithBlocks', true)
}
} catch (error) {
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to block user",
});
}
};
const blockUserFromOutsideModalFunc = (e) => {
const user = e.detail?.user;
setIsOpenBlockedModal(true)
blockUser(null, user)
};
useEffect(() => {
subscribeToEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
return () => {
unsubscribeFromEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
};
}, []);
return (
<Dialog
open={true}
open={isOpenBlockedModal}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle>Blocked Users</DialogTitle>
<DialogContent sx={{
padding: '20px'
}}>
<DialogContent
sx={{
padding: "20px",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
@ -49,39 +156,42 @@ export const BlockedUsersModal = ({ close }) => {
}}
>
<TextField
placeholder="Name"
placeholder="Name or address"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<Button variant="contained" onClick={async ()=> {
try {
if(!value) return
await addToBlockList(undefined, value)
fetchBlockedUsers()
setHasChanged(true)
} catch (error) {
console.error(error)
}
}}>Block</Button>
<Button
sx={{
flexShrink: 0,
}}
variant="contained"
onClick={blockUser}
>
Block
</Button>
</Box>
{Object.entries(blockedUsers?.addresses).length > 0 && (
<>
<Spacer height="20px" />
<DialogContentText id="alert-dialog-description">
Blocked Users for Chat ( addresses )
Blocked addresses- blocks processing of txs
</DialogContentText>
<Spacer height="10px" />
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
<Spacer height="10px" />
</>
)}
<Box sx={{
display: 'flex',
flexDirection: 'column',
gap: '10px'
}}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
{Object.entries(blockedUsers?.addresses || {})?.map(
([key, value]) => {
return (
@ -90,18 +200,22 @@ export const BlockedUsersModal = ({ close }) => {
display: "flex",
alignItems: "center",
gap: "10px",
width: '100%',
justifyContent: 'space-between'
width: "100%",
justifyContent: "space-between",
}}
>
<Typography>{key}</Typography>
<Typography>{addressesWithNames[key] || key}</Typography>
<Button
sx={{
flexShrink: 0,
}}
size="small"
variant="contained"
onClick={async () => {
try {
await removeBlockFromList(key, undefined);
setHasChanged(true);
setValue('')
setValue("");
fetchBlockedUsers();
} catch (error) {
console.error(error);
@ -119,17 +233,19 @@ export const BlockedUsersModal = ({ close }) => {
<>
<Spacer height="20px" />
<DialogContentText id="alert-dialog-description">
Blocked Users for QDN and Chat (names)
Blocked names for QDN
</DialogContentText>
<Spacer height="10px" />
</>
)}
<Box sx={{
display: 'flex',
flexDirection: 'column',
gap: '10px'
}}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
return (
<Box
@ -137,12 +253,16 @@ export const BlockedUsersModal = ({ close }) => {
display: "flex",
alignItems: "center",
gap: "10px",
width: '100%',
justifyContent: 'space-between'
width: "100%",
justifyContent: "space-between",
}}
>
<Typography>{key}</Typography>
<Button
size="small"
sx={{
flexShrink: 0,
}}
variant="contained"
onClick={async () => {
try {
@ -175,16 +295,67 @@ export const BlockedUsersModal = ({ close }) => {
},
}}
variant="contained"
onClick={()=> {
if(hasChanged){
executeEvent('updateChatMessagesWithBlocks', true)
onClick={() => {
if (hasChanged) {
executeEvent("updateChatMessagesWithBlocks", true);
}
close()
setIsOpenBlockedModal(false);
}}
>
close
</Button>
</DialogActions>
<Dialog
open={isShow}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"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>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => {
onOk("address");
}}
>
Block txs
</Button>
<Button
variant="contained"
onClick={() => {
onOk("name");
}}
>
Block QDN data
</Button>
<Button
variant="contained"
onClick={() => {
onOk("both");
}}
>
Block All
</Button>
</DialogActions>
</Dialog>
</Dialog>
);
};

View File

@ -1,329 +0,0 @@
import React, {
FC,
useCallback,
useEffect,
useRef,
useState
} from 'react'
import {
Box,
Skeleton,
} from '@mui/material'
import { ShowMessage } from './ShowMessageWithoutModal'
// import {
// setIsLoadingCustom,
// } from '../../state/features/globalSlice'
import { ComposeP, GroupContainer, GroupNameP, MailIconImg, ShowMessageReturnButton, SingleThreadParent, ThreadContainer, ThreadContainerFullWidth } from './Mail-styles'
import { Spacer } from '../../../common/Spacer'
import { threadIdentifier } from './GroupMail'
import LazyLoad from '../../../common/LazyLoad'
import ReturnSVG from '../../../assets/svgs/Return.svg'
import { NewThread } from './NewThread'
import { decryptPublishes } from '../../Chat/GroupAnnouncements'
import { getBaseApi } from '../../../background'
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App'
interface ThreadProps {
currentThread: any
groupInfo: any
closeThread: () => void
members: any
}
const getEncryptedResource = async ({name, identifier, secretKey})=> {
const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT/${name}/${identifier}?encoding=base64`
);
const data = await res.text();
const response = await decryptPublishes([{ data }], secretKey);
const messageData = response[0];
return messageData.decryptedData
}
export const Thread = ({
currentThread,
groupInfo,
closeThread,
members,
userInfo,
secretKey,
getSecretKey
}: ThreadProps) => {
const [messages, setMessages] = useState<any[]>([])
const [hashMapMailMessages, setHashMapMailMessages] = useState({})
const secretKeyRef = useRef(null)
useEffect(() => {
secretKeyRef.current = secretKey;
}, [secretKey]);
const getIndividualMsg = async (message: any) => {
try {
const responseDataMessage = await getEncryptedResource({identifier: message.identifier, name: message.name, secretKey})
const fullObject = {
...message,
...(responseDataMessage || {}),
id: message.identifier
}
setHashMapMailMessages((prev)=> {
return {
...prev,
[message.identifier]: fullObject
}
})
} catch (error) {}
}
const getMailMessages = React.useCallback(
async (groupInfo: any, reset?: boolean, hideAlert?: boolean) => {
try {
if(!hideAlert){
// dispatch(setIsLoadingCustom('Loading messages'))
}
let threadId = groupInfo.threadId
const offset = messages.length
const identifier = `thmsg-${threadId}`
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
const responseData = await response.json()
let fullArrayMsg = reset ? [] : [...messages]
let newMessages: any[] = []
for (const message of responseData) {
const index = fullArrayMsg.findIndex(
(p) => p.identifier === message.identifier
)
if (index !== -1) {
fullArrayMsg[index] = message
} else {
fullArrayMsg.push(message)
getIndividualMsg(message)
}
}
setMessages(fullArrayMsg)
} catch (error) {
} finally {
if(!hideAlert){
// dispatch(setIsLoadingCustom(null))
}
}
},
[messages, secretKey]
)
const getMessages = React.useCallback(async () => {
if (!currentThread || !secretKey) return
await getMailMessages(currentThread, true)
}, [getMailMessages, currentThread, secretKey])
const firstMount = useRef(false)
const saveTimestamp = useCallback((currentThread: any, username?: string)=> {
if(!currentThread?.threadData?.groupId || !currentThread?.threadId || !username) return
const threadIdForLocalStorage = `qmail_threads_${currentThread?.threadData?.groupId}_${currentThread?.threadId}`
const threads = JSON.parse(
localStorage.getItem(`qmail_threads_viewedtimestamp_${username}`) || "{}"
);
// Convert to an array of objects with identifier and all fields
let dataArray = Object.entries(threads).map(([identifier, value]) => ({
identifier,
...(value as any),
}));
// Sort the array based on timestamp in descending order
dataArray.sort((a, b) => b.timestamp - a.timestamp);
// Slice the array to keep only the first 500 elements
let latest500 = dataArray.slice(0, 500);
// Convert back to the original object format
let latest500Data: any = {};
latest500.forEach(item => {
const { identifier, ...rest } = item;
latest500Data[identifier] = rest;
});
latest500Data[threadIdForLocalStorage] = {
timestamp: Date.now(),
}
localStorage.setItem(
`qmail_threads_viewedtimestamp_${username}`,
JSON.stringify(latest500Data)
);
}, [])
useEffect(() => {
if (currentThread && secretKey) {
getMessages()
firstMount.current = true
// saveTimestamp(currentThread, user.name)
}
}, [ currentThread, secretKey])
const messageCallback = useCallback((msg: any) => {
// dispatch(addToHashMapMail(msg))
setMessages((prev) => [msg, ...prev])
}, [])
const interval = useRef<any>(null)
const checkNewMessages = React.useCallback(
async (groupInfo: any) => {
try {
let threadId = groupInfo.threadId
const identifier = `thmsg-${threadId}`
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
const responseData = await response.json()
const latestMessage = messages[0]
if (!latestMessage) return
const findMessage = responseData?.findIndex(
(item: any) => item?.identifier === latestMessage?.identifier
)
let sliceLength = responseData.length
if (findMessage !== -1) {
sliceLength = findMessage
}
const newArray = responseData.slice(0, findMessage).reverse()
let fullArrayMsg = [...messages]
for (const message of newArray) {
try {
const responseDataMessage = await getEncryptedResource({identifier: message.identifier, name: message.name, secretKey: secretKeyRef.current})
const fullObject = {
...message,
...(responseDataMessage || {}),
id: message.identifier
}
setHashMapMailMessages((prev)=> {
return {
...prev,
[message.identifier]: fullObject
}
})
const index = messages.findIndex(
(p) => p.identifier === fullObject.identifier
)
if (index !== -1) {
fullArrayMsg[index] = fullObject
} else {
fullArrayMsg.unshift(fullObject)
}
} catch (error) {}
}
setMessages(fullArrayMsg)
} catch (error) {
} finally {
}
},
[messages]
)
const checkNewMessagesFunc = useCallback(() => {
let isCalling = false
interval.current = setInterval(async () => {
if (isCalling) return
isCalling = true
const res = await checkNewMessages(currentThread)
isCalling = false
}, 8000)
}, [checkNewMessages, currentThread])
useEffect(() => {
checkNewMessagesFunc()
return () => {
if (interval?.current) {
clearInterval(interval.current)
}
}
}, [checkNewMessagesFunc])
if (!currentThread) return null
return (
<GroupContainer
sx={{
position: "relative",
overflow: 'auto',
width: '100%'
}}
>
<NewThread
groupInfo={groupInfo}
isMessage={true}
currentThread={currentThread}
messageCallback={messageCallback}
members={members}
userInfo={userInfo}
getSecretKey={getSecretKey}
/>
<ThreadContainerFullWidth>
<ThreadContainer>
<Spacer height="30px" />
<Box sx={{
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between'
}}>
<GroupNameP>{currentThread?.threadData?.title}</GroupNameP>
<ShowMessageReturnButton onClick={() => {
setMessages([])
closeThread()
}}>
<MailIconImg src={ReturnSVG} />
<ComposeP>Return to Threads</ComposeP>
</ShowMessageReturnButton>
</Box>
<Spacer height="60px" />
{messages.map((message) => {
let fullMessage = message
if (hashMapMailMessages[message?.identifier]) {
fullMessage = hashMapMailMessages[message.identifier]
return <ShowMessage key={message?.identifier} message={fullMessage} />
}
return (
<SingleThreadParent>
<Skeleton
variant="rectangular"
style={{
width: '100%',
height: 60,
borderRadius: '8px',
overflow: 'hidden'
}}
/>
</SingleThreadParent>
)
})}
</ThreadContainer>
</ThreadContainerFullWidth>
{messages.length >= 20 && (
<LazyLoad onLoadMore={()=> getMailMessages(currentThread, false, true)}></LazyLoad>
)}
</GroupContainer>
)
}

View File

@ -5,7 +5,7 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
} from 'react';
import {
Avatar,
Box,
@ -14,8 +14,8 @@ import {
IconButton,
Skeleton,
Typography,
} from "@mui/material";
import { ShowMessage } from "./ShowMessageWithoutModal";
} from '@mui/material';
import { ShowMessage } from './ShowMessageWithoutModal';
import {
ComposeP,
GroupContainer,
@ -28,34 +28,33 @@ import {
ThreadInfoColumn,
ThreadInfoColumnNameP,
ThreadInfoColumnTime,
} from "./Mail-styles";
import { Spacer } from "../../../common/Spacer";
import { threadIdentifier } from "./GroupMail";
import LazyLoad from "../../../common/LazyLoad";
import ReturnSVG from "../../../assets/svgs/Return.svg";
import { NewThread } from "./NewThread";
} from './Mail-styles';
import { Spacer } from '../../../common/Spacer';
import { threadIdentifier } from './GroupMail';
import ReturnSVG from '../../../assets/svgs/Return.svg';
import { NewThread } from './NewThread';
import {
decryptPublishes,
getTempPublish,
handleUnencryptedPublishes,
} from "../../Chat/GroupAnnouncements";
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar";
import { subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events";
import RefreshIcon from "@mui/icons-material/Refresh";
} from '../../Chat/GroupAnnouncements';
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import { subscribeToEvent, unsubscribeFromEvent } from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh';
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from "../../../App";
} from '../../../App';
import {
ArrowDownward as ArrowDownwardIcon,
ArrowUpward as ArrowUpwardIcon,
} from "@mui/icons-material";
import { addDataPublishesFunc, getDataPublishesFunc } from "../Group";
import { RequestQueueWithPromise } from "../../../utils/queue/queue";
import { CustomLoader } from "../../../common/CustomLoader";
import { WrapperUserAction } from "../../WrapperUserAction";
import { formatTimestampForum } from "../../../utils/time";
} from '@mui/icons-material';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
import { RequestQueueWithPromise } from '../../../utils/queue/queue';
import { CustomLoader } from '../../../common/CustomLoader';
import { WrapperUserAction } from '../../WrapperUserAction';
import { formatTimestampForum } from '../../../utils/time';
const requestQueueSaveToLocal = new RequestQueueWithPromise(1);
const requestQueueDownloadPost = new RequestQueueWithPromise(3);
interface ThreadProps {
@ -65,14 +64,10 @@ interface ThreadProps {
members: any;
}
const getEncryptedResource = async ({
name,
identifier,
secretKey,
resource,
groupId,
dataPublishes,
}, isPrivate) => {
const getEncryptedResource = async (
{ name, identifier, secretKey, resource, groupId, dataPublishes },
isPrivate
) => {
let data = dataPublishes[`${name}-${identifier}`];
if (
!data ||
@ -93,14 +88,17 @@ const getEncryptedResource = async ({
}
data = await res.text();
if (data?.error || typeof data !== "string") return;
if (data?.error || typeof data !== 'string') return;
await requestQueueSaveToLocal.enqueue(() => {
return addDataPublishesFunc({ ...resource, data }, groupId, "thmsg");
return addDataPublishesFunc({ ...resource, data }, groupId, 'thmsg');
});
} else {
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;
@ -115,7 +113,7 @@ export const Thread = ({
secretKey,
getSecretKey,
updateThreadActivityCurrentThread,
isPrivate
isPrivate,
}: ThreadProps) => {
const [tempPublishedList, setTempPublishedList] = useState([]);
const [messages, setMessages] = useState<any[]>([]);
@ -129,7 +127,7 @@ export const Thread = ({
// Update: Use a new ref for the scrollable container
const threadContainerRef = useRef(null);
const threadBeginningRef = useRef(null)
const threadBeginningRef = useRef(null);
// New state variables
const [showScrollButton, setShowScrollButton] = useState(false);
const [isAtBottom, setIsAtBottom] = useState(false);
@ -140,7 +138,7 @@ export const Thread = ({
const dataPublishes = useRef({});
const getSavedData = useCallback(async (groupId) => {
const res = await getDataPublishesFunc(groupId, "thmsg");
const res = await getDataPublishesFunc(groupId, 'thmsg');
dataPublishes.current = res || {};
}, []);
@ -159,14 +157,17 @@ export const Thread = ({
const getIndividualMsg = async (message: any) => {
try {
const responseDataMessage = await getEncryptedResource({
const responseDataMessage = await getEncryptedResource(
{
identifier: message.identifier,
name: message.name,
secretKey,
resource: message,
groupId: groupInfo?.groupId,
dataPublishes: dataPublishes.current,
}, isPrivate);
},
isPrivate
);
if (responseDataMessage?.error) {
const fullObject = {
@ -201,7 +202,7 @@ export const Thread = ({
try {
let threadId = currentThread.threadId;
const keyTemp = "thread-post";
const keyTemp = 'thread-post';
const getTempAnnouncements = await getTempPublish();
if (getTempAnnouncements?.[keyTemp]) {
@ -232,10 +233,10 @@ export const Thread = ({
const identifier = `thmsg-${threadId}`;
let url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&prefix=true`;
if (!isReverse) {
url = url + "&reverse=false";
url = url + '&reverse=false';
}
if (isReverse) {
url = url + "&reverse=true";
url = url + '&reverse=true';
}
if (after) {
url = url + `&after=${after}`;
@ -245,9 +246,9 @@ export const Thread = ({
}
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -263,14 +264,13 @@ export const Thread = ({
setMessages(fullArrayMsg);
if (before === null && after === null && isReverse) {
setTimeout(() => {
containerRef.current.scrollIntoView({ behavior: "smooth" });
containerRef.current.scrollIntoView({ behavior: 'smooth' });
}, 300);
}
if(after || before === null && after === null && !isReverse){
if (after || (before === null && after === null && !isReverse)) {
setTimeout(() => {
threadBeginningRef.current.scrollIntoView();
}, 100);
}
if (fullArrayMsg.length === 0) {
@ -282,9 +282,9 @@ export const Thread = ({
fullArrayMsg[0].created
}`;
const responseNewer = await fetch(urlNewer, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseDataNewer = await responseNewer.json();
@ -300,9 +300,9 @@ export const Thread = ({
fullArrayMsg[fullArrayMsg.length - 1].created
}`;
const responseOlder = await fetch(urlOlder, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseDataOlder = await responseOlder.json();
@ -316,7 +316,7 @@ export const Thread = ({
updateThreadActivityCurrentThread();
}
} catch (error) {
console.log("error", error);
console.log('error', error);
} finally {
setIsLoading(false);
getSavedData(groupId);
@ -325,9 +325,21 @@ export const Thread = ({
[messages, secretKey]
);
const getMessages = React.useCallback(async () => {
if (!currentThread || (!secretKey && isPrivate) || !groupInfo?.groupId || isPrivate === null) return;
if (
!currentThread ||
(!secretKey && isPrivate) ||
!groupInfo?.groupId ||
isPrivate === null
)
return;
await getMailMessages(currentThread, null, null, false, groupInfo?.groupId);
}, [getMailMessages, currentThread, secretKey, groupInfo?.groupId, isPrivate]);
}, [
getMailMessages,
currentThread,
secretKey,
groupInfo?.groupId,
isPrivate,
]);
const firstMount = useRef(false);
const saveTimestamp = useCallback((currentThread: any, username?: string) => {
@ -339,7 +351,7 @@ export const Thread = ({
return;
const threadIdForLocalStorage = `qmail_threads_${currentThread?.threadData?.groupId}_${currentThread?.threadId}`;
const threads = JSON.parse(
localStorage.getItem(`qmail_threads_viewedtimestamp_${username}`) || "{}"
localStorage.getItem(`qmail_threads_viewedtimestamp_${username}`) || '{}'
);
// Convert to an array of objects with identifier and all fields
let dataArray = Object.entries(threads).map(([identifier, value]) => ({
@ -382,7 +394,7 @@ export const Thread = ({
if (currentThreadRef.current?.threadId !== currentThread?.threadId) {
firstMount.current = false;
}
if(!secretKey && isPrivate) return
if (!secretKey && isPrivate) return;
if (currentThread && !firstMount.current && isPrivate !== null) {
getMessagesMiddleware();
}
@ -402,9 +414,9 @@ export const Thread = ({
const identifier = `thmsg-${threadId}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
@ -469,17 +481,17 @@ export const Thread = ({
const threadFetchModeFunc = (e) => {
const mode = e.detail?.mode;
if (mode === "last-page") {
if (mode === 'last-page') {
getMailMessages(currentThread, null, null, true, groupInfo?.groupId);
}
firstMount.current = true;
};
React.useEffect(() => {
subscribeToEvent("threadFetchMode", threadFetchModeFunc);
subscribeToEvent('threadFetchMode', threadFetchModeFunc);
return () => {
unsubscribeFromEvent("threadFetchMode", threadFetchModeFunc);
unsubscribeFromEvent('threadFetchMode', threadFetchModeFunc);
};
}, []);
@ -526,11 +538,11 @@ export const Thread = ({
handleScroll();
}, 400);
container.addEventListener("scroll", handleScroll);
container.addEventListener('scroll', handleScroll);
// Cleanup
return () => {
container.removeEventListener("scroll", handleScroll);
container.removeEventListener('scroll', handleScroll);
};
}, [messages]);
@ -540,9 +552,9 @@ export const Thread = ({
if (!container) return;
if (isAtBottom) {
container.scrollTo({ top: 0, behavior: "smooth" }); // Scroll to top
container.scrollTo({ top: 0, behavior: 'smooth' }); // Scroll to top
} else {
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" }); // Scroll to bottom
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // Scroll to bottom
}
};
@ -550,19 +562,19 @@ export const Thread = ({
return (
<GroupContainer
sx={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
overflow: "hidden",
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
// Removed the ref from here since the scrollable area has changed
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexShrink: 0, // Corrected property name
}}
>
@ -583,16 +595,16 @@ export const Thread = ({
/>
<Box
sx={{
display: "flex",
gap: isMobile ? "45px" : "35px",
alignItems: "center",
padding: isMobile && "5px",
display: 'flex',
gap: isMobile ? '45px' : '35px',
alignItems: 'center',
padding: isMobile && '5px',
}}
>
<ShowMessageReturnButton
sx={{
padding: isMobile && "5px",
minWidth: isMobile && "50px",
padding: isMobile && '5px',
minWidth: isMobile && '50px',
}}
onClick={() => {
setMessages([]);
@ -608,9 +620,9 @@ export const Thread = ({
<ButtonBase onClick={scrollToPosition}>
<ArrowUpwardIcon
sx={{
color: "white",
cursor: "pointer",
fontSize: isMobile ? "28px" : "36px",
color: 'white',
cursor: 'pointer',
fontSize: isMobile ? '28px' : '36px',
}}
/>
</ButtonBase>
@ -618,9 +630,9 @@ export const Thread = ({
<ButtonBase onClick={scrollToPosition}>
<ArrowDownwardIcon
sx={{
color: "white",
cursor: "pointer",
fontSize: isMobile ? "28px" : "36px",
color: 'white',
cursor: 'pointer',
fontSize: isMobile ? '28px' : '36px',
}}
/>
</ButtonBase>
@ -631,45 +643,45 @@ export const Thread = ({
<ThreadContainerFullWidth
sx={{
flexGrow: 1,
overflow: "auto",
overflow: 'auto',
}}
ref={threadContainerRef} // Updated ref attached here
>
<div ref={threadBeginningRef}/>
<div ref={threadBeginningRef} />
<ThreadContainer>
<Spacer height={isMobile ? "10px" : "30px"} />
<Spacer height={isMobile ? '10px' : '30px'} />
<Box
sx={{
width: "100%",
alignItems: "center",
display: "flex",
justifyContent: "space-between",
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
}}
>
<GroupNameP
sx={{
fontSize: isMobile && "18px",
fontSize: isMobile && '18px',
}}
>
{currentThread?.threadData?.title}
</GroupNameP>
</Box>
<Spacer height={"15px"} />
<Spacer height={'15px'} />
<Box
sx={{
width: "100%",
alignItems: "center",
display: "flex",
justifyContent: "center",
gap: "5px",
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
gap: '5px',
}}
>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -687,9 +699,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -707,9 +719,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -727,9 +739,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -746,35 +758,34 @@ export const Thread = ({
Last
</Button>
</Box>
<Spacer height={isMobile ? "10px" : "30px"} />
<Spacer height={isMobile ? '10px' : '30px'} />
{combinedListTempAndReal.map((message, index, list) => {
let fullMessage = message;
if (hashMapMailMessages[message?.identifier]) {
fullMessage = hashMapMailMessages[message.identifier];
if (fullMessage?.error) {
return (
<SingleThreadParent
sx={{
height: "auto",
height: 'auto',
}}
>
<Box
style={{
width: "100%",
borderRadius: "8px",
overflow: "hidden",
position: "relative",
flexDirection: "column",
width: '100%',
borderRadius: '8px',
overflow: 'hidden',
position: 'relative',
flexDirection: 'column',
}}
>
<Box
sx={{
display: "flex",
alignItems: "flex-start",
gap: "10px",
display: 'flex',
alignItems: 'flex-start',
gap: '10px',
}}
>
<WrapperUserAction
@ -784,8 +795,8 @@ export const Thread = ({
>
<Avatar
sx={{
height: "50px",
width: "50px",
height: '50px',
width: '50px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
message?.name
@ -812,23 +823,22 @@ export const Thread = ({
</Box>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<Typography
sx={{
fontSize: "18px",
color: "white",
fontSize: '18px',
color: 'white',
}}
>
{fullMessage?.error}
</Typography>
</Box>
</Box>
</SingleThreadParent>
);
@ -855,23 +865,23 @@ export const Thread = ({
return (
<SingleThreadParent
sx={{
height: "auto",
height: 'auto',
}}
>
<Box
style={{
width: "100%",
borderRadius: "8px",
overflow: "hidden",
position: "relative",
flexDirection: "column",
width: '100%',
borderRadius: '8px',
overflow: 'hidden',
position: 'relative',
flexDirection: 'column',
}}
>
<Box
sx={{
display: "flex",
alignItems: "flex-start",
gap: "10px",
display: 'flex',
alignItems: 'flex-start',
gap: '10px',
}}
>
<WrapperUserAction
@ -881,8 +891,8 @@ export const Thread = ({
>
<Avatar
sx={{
height: "50px",
width: "50px",
height: '50px',
width: '50px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
message?.name
@ -909,24 +919,23 @@ export const Thread = ({
</Box>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<CustomLoader />
<Typography
sx={{
fontSize: "18px",
color: "white",
fontSize: '18px',
color: 'white',
}}
>
Downloading from QDN
</Typography>
</Box>
</Box>
</SingleThreadParent>
);
@ -937,9 +946,9 @@ export const Thread = ({
<Spacer height="20px" />
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "flex-end",
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
}}
>
<Button
@ -955,7 +964,7 @@ export const Thread = ({
);
}}
sx={{
color: "white",
color: 'white',
}}
>
Refetch page
@ -964,26 +973,27 @@ export const Thread = ({
</>
)}
<Box sx={{
<Box
sx={{
width: '100%',
visibility: messages?.length > 4 ? 'visible' : 'hidden'
}}>
visibility: messages?.length > 4 ? 'visible' : 'hidden',
}}
>
<Spacer height="30px" />
<Box
sx={{
width: "100%",
alignItems: "center",
display: "flex",
justifyContent: "center",
gap: "5px",
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
gap: '5px',
}}
>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -1001,9 +1011,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -1021,9 +1031,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -1041,9 +1051,9 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
getMailMessages(
@ -1069,7 +1079,7 @@ export const Thread = ({
<LoadingSnackbar
open={isLoading}
info={{
message: "Loading posts... please wait.",
message: 'Loading posts... please wait.',
}}
/>
</GroupContainer>

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ export const useBlockedAddresses = () => {
const isUserBlocked = useCallback((address, name)=> {
try {
if(!address) return false
if(userBlockedRef.current[address] || userNamesBlockedRef.current[name]) return true
if(userBlockedRef.current[address]) return true
return false
@ -90,12 +90,13 @@ export const useBlockedAddresses = () => {
}, [])
const removeBlockFromList = useCallback(async (address, name)=> {
if(name){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: name ? [name] : [address],
listName: name ? 'blockedNames' : 'blockedAddresses'
items: [name] ,
listName: 'blockedNames'
})
.then((response) => {
@ -103,15 +104,11 @@ export const useBlockedAddresses = () => {
rej(response?.message);
return;
} else {
if(!name){
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
} else {
const copyObject = {...userNamesBlockedRef.current}
delete copyObject[name]
userNamesBlockedRef.current = copyObject
}
res(response);
}
@ -120,13 +117,15 @@ export const useBlockedAddresses = () => {
console.error("Failed qortalRequest", error);
});
})
if(name && userBlockedRef.current[address]){
}
if(address){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: !name ? [name] : [address],
listName: !name ? 'blockedNames' : 'blockedAddresses'
items: [address],
listName: 'blockedAddresses'
})
.then((response) => {
@ -134,9 +133,12 @@ export const useBlockedAddresses = () => {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
res(response);
}
})
@ -146,15 +148,17 @@ export const useBlockedAddresses = () => {
})
}
}, [])
const addToBlockList = useCallback(async (address, name)=> {
if(name){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'add',
items: name ? [name] : [address],
listName: name ? 'blockedNames' : 'blockedAddresses'
items: [name],
listName: 'blockedNames'
})
.then((response) => {
@ -162,17 +166,10 @@ export const useBlockedAddresses = () => {
rej(response?.message);
return;
} else {
if(name){
const copyObject = {...userNamesBlockedRef.current}
copyObject[name] = true
userNamesBlockedRef.current = copyObject
}else {
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
}
res(response);
}
@ -181,6 +178,35 @@ export const useBlockedAddresses = () => {
console.error("Failed qortalRequest", error);
});
})
}
if(address){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'add',
items: [address],
listName: 'blockedAddresses'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
}
}, [])
return {

View File

@ -13,29 +13,29 @@ import {
InputLabel,
Snackbar,
Typography,
} from "@mui/material";
} from '@mui/material';
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import CloseIcon from "@mui/icons-material/Close";
import { MyContext, getBaseApiReact } from "../../App";
} from 'react';
import CloseIcon from '@mui/icons-material/Close';
import { MyContext, 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 "../../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";
} 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';
export const Minting = ({
setIsOpenMinting,
@ -47,9 +47,9 @@ export const Minting = ({
}) => {
const [mintingAccounts, setMintingAccounts] = useState([]);
const [accountInfo, setAccountInfo] = useState(null);
const [rewardSharePublicKey, setRewardSharePublicKey] = useState("");
const [mintingKey, setMintingKey] = useState("");
const [rewardsharekey, setRewardsharekey] = useState("");
const [rewardSharePublicKey, setRewardSharePublicKey] = useState('');
const [mintingKey, setMintingKey] = useState('');
const [rewardsharekey, setRewardsharekey] = useState('');
const [rewardShares, setRewardShares] = useState([]);
const [nodeInfos, setNodeInfos] = useState({});
const [openSnack, setOpenSnack] = useState(false);
@ -60,17 +60,17 @@ export const Minting = ({
const [info, setInfo] = useState(null);
const [names, setNames] = useState({});
const [accountInfos, setAccountInfos] = useState({});
const [showWaitDialog, setShowWaitDialog] = useState(false)
const [showWaitDialog, setShowWaitDialog] = useState(false);
const isPartOfMintingGroup = useMemo(() => {
if (groups?.length === 0) return false;
return !!groups?.find((item) => item?.groupId?.toString() === "694");
return !!groups?.find((item) => item?.groupId?.toString() === '694');
}, [groups]);
const getMintingAccounts = useCallback(async () => {
try {
const url = `${getBaseApiReact()}/admin/mintingaccounts`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("network error");
throw new Error('network error');
}
const data = await response.json();
setMintingAccounts(data);
@ -117,7 +117,7 @@ export const Minting = ({
const url = `${getBaseApiReact()}/addresses/${address}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("network error");
throw new Error('network error');
}
const data = await response.json();
if (others) {
@ -144,10 +144,10 @@ export const Minting = ({
};
useEffect(() => {
subscribeToEvent("refresh-rewardshare-list", refreshRewardShare);
subscribeToEvent('refresh-rewardshare-list', refreshRewardShare);
return () => {
unsubscribeFromEvent("refresh-rewardshare-list", refreshRewardShare);
unsubscribeFromEvent('refresh-rewardshare-list', refreshRewardShare);
};
}, [myAddress]);
@ -177,15 +177,15 @@ export const Minting = ({
try {
const url = `${getBaseApiReact()}/admin/status`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const data = await response.json();
setNodeInfos(data);
} catch (error) {
console.error("Request failed", error);
console.error('Request failed', error);
}
};
@ -194,11 +194,11 @@ export const Minting = ({
const url = `${getBaseApiReact()}/addresses/rewardshares?involving=${address}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("network error");
throw new Error('network error');
}
const data = await response.json();
setRewardShares(data);
return data
return data;
} catch (error) {}
}, []);
@ -208,10 +208,10 @@ export const Minting = ({
return await new Promise((res, rej) => {
window
.sendMessage(
"ADMIN_ACTION",
'ADMIN_ACTION',
{
type: "addmintingaccount",
type: 'addmintingaccount',
value: val,
},
180000,
@ -220,7 +220,7 @@ export const Minting = ({
.then((response) => {
if (!response?.error) {
res(response);
setMintingKey("");
setMintingKey('');
setTimeout(() => {
getMintingAccounts();
}, 300);
@ -229,13 +229,13 @@ export const Minting = ({
rej({ message: response.error });
})
.catch((error) => {
rej({ message: error.message || "An error occurred" });
rej({ message: error.message || 'An error occurred' });
});
});
} catch (error) {
setInfo({
type: "error",
message: error?.message || "Unable to add minting account",
type: 'error',
message: error?.message || 'Unable to add minting account',
});
setOpenSnack(true);
} finally {
@ -249,10 +249,10 @@ export const Minting = ({
return await new Promise((res, rej) => {
window
.sendMessage(
"ADMIN_ACTION",
'ADMIN_ACTION',
{
type: "removemintingaccount",
type: 'removemintingaccount',
value: val,
},
180000,
@ -270,13 +270,13 @@ export const Minting = ({
rej({ message: response.error });
})
.catch((error) => {
rej({ message: error.message || "An error occurred" });
rej({ message: error.message || 'An error occurred' });
});
});
} catch (error) {
setInfo({
type: "error",
message: error?.message || "Unable to remove minting account",
type: 'error',
message: error?.message || 'Unable to remove minting account',
});
setOpenSnack(true);
} finally {
@ -285,14 +285,14 @@ export const Minting = ({
}, []);
const createRewardShare = useCallback(async (publicKey, recipient) => {
const fee = await getFee("REWARD_SHARE");
const fee = await getFee('REWARD_SHARE');
await show({
message: "Would you like to perform an REWARD_SHARE transaction?",
publishFee: fee.fee + " QORT",
message: 'Would you like to perform an REWARD_SHARE transaction?',
publishFee: fee.fee + ' QORT',
});
return await new Promise((res, rej) => {
window
.sendMessage("createRewardShare", {
.sendMessage('createRewardShare', {
recipientPublicKey: publicKey,
})
.then((response) => {
@ -301,7 +301,7 @@ export const Minting = ({
{
recipient,
...response,
type: "add-rewardShare",
type: 'add-rewardShare',
label: `Add rewardshare: awaiting confirmation`,
labelDone: `Add rewardshare: success!`,
done: false,
@ -314,7 +314,7 @@ export const Minting = ({
rej({ message: response.error });
})
.catch((error) => {
rej({ message: error.message || "An error occurred" });
rej({ message: error.message || 'An error occurred' });
});
});
}, []);
@ -322,7 +322,7 @@ export const Minting = ({
const getRewardSharePrivateKey = useCallback(async (publicKey) => {
return await new Promise((res, rej) => {
window
.sendMessage("getRewardSharePrivateKey", {
.sendMessage('getRewardSharePrivateKey', {
recipientPublicKey: publicKey,
})
.then((response) => {
@ -333,7 +333,7 @@ export const Minting = ({
rej({ message: response.error });
})
.catch((error) => {
rej({ message: error.message || "An error occurred" });
rej({ message: error.message || 'An error occurred' });
});
});
}, []);
@ -345,7 +345,6 @@ export const Minting = ({
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
while (Date.now() - startTime < timeoutMs) {
const rewardShares = await getRewardShares(myAddress);
const findRewardShare = rewardShares?.find(
(item) =>
@ -356,11 +355,10 @@ export const Minting = ({
return true; // Exit early if found
}
await sleep(pollingInterval); // Wait before the next poll
}
throw new Error("Timeout waiting for reward share confirmation");
throw new Error('Timeout waiting for reward share confirmation');
};
const startMinting = async () => {
@ -377,23 +375,22 @@ export const Minting = ({
addMintingAccount(privateRewardShare);
} else {
await createRewardShare(accountInfo?.publicKey, myAddress);
setShowWaitDialog(true)
await waitUntilRewardShareIsConfirmed()
setShowWaitDialog(true);
await waitUntilRewardShareIsConfirmed();
await showNext({
message: ''
})
message: '',
});
const privateRewardShare = await getRewardSharePrivateKey(
accountInfo?.publicKey
);
setShowWaitDialog(false)
setShowWaitDialog(false);
addMintingAccount(privateRewardShare);
}
} catch (error) {
setShowWaitDialog(false)
setShowWaitDialog(false);
setInfo({
type: "error",
message: error?.message || "Unable to start minting",
type: 'error',
message: error?.message || 'Unable to start minting',
});
setOpenSnack(true);
} finally {
@ -412,13 +409,13 @@ export const Minting = ({
const url = `${getBaseApiReact()}/groups/member/${address}`;
const response = await fetch(url);
const data = await response.json();
return !!data?.find((grp) => grp?.groupId?.toString() === "694");
return !!data?.find((grp) => grp?.groupId?.toString() === '694');
};
const removeRewardShare = useCallback(async (rewardShare) => {
return await new Promise((res, rej) => {
window
.sendMessage("removeRewardShare", {
.sendMessage('removeRewardShare', {
rewardShareKeyPairPublicKey: rewardShare.rewardSharePublicKey,
recipient: rewardShare.recipient,
percentageShare: -1,
@ -430,7 +427,7 @@ export const Minting = ({
{
...rewardShare,
...response,
type: "remove-rewardShare",
type: 'remove-rewardShare',
label: `Remove rewardshare: awaiting confirmation`,
labelDone: `Remove rewardshare: success!`,
done: false,
@ -442,7 +439,7 @@ export const Minting = ({
rej({ message: response.error });
})
.catch((error) => {
rej({ message: error.message || "An error occurred" });
rej({ message: error.message || 'An error occurred' });
});
});
}, []);
@ -454,8 +451,8 @@ export const Minting = ({
const privateRewardShare = await removeRewardShare(rewardShare);
} catch (error) {
setInfo({
type: "error",
message: error?.message || "Unable to remove reward share",
type: 'error',
message: error?.message || 'Unable to remove reward share',
});
setOpenSnack(true);
} finally {
@ -468,9 +465,9 @@ export const Minting = ({
setIsLoading(true);
const confirmReceiver = await getNameOrAddress(receiver);
if (confirmReceiver.error)
throw new Error("Invalid receiver address or name");
throw new Error('Invalid receiver address or name');
const isInMinterGroup = await checkIfMinterGroup(confirmReceiver);
if (!isInMinterGroup) throw new Error("Account not in Minter Group");
if (!isInMinterGroup) throw new Error('Account not in Minter Group');
const publicKey = await getPublicKeyFromAddress(confirmReceiver);
const findRewardShare = rewardShares?.find(
(item) =>
@ -487,8 +484,8 @@ export const Minting = ({
}
} catch (error) {
setInfo({
type: "error",
message: error?.message || "Unable to create reward share",
type: 'error',
message: error?.message || 'Unable to create reward share',
});
setOpenSnack(true);
} finally {
@ -550,11 +547,9 @@ export const Minting = ({
(accountInfo?.blocksMinted + accountInfo?.blocksMintedAdjustment);
let countBlocksString = countBlocks.toString();
return "" + countBlocksString;
return '' + countBlocksString;
};
return (
<Dialog
open={true}
@ -562,19 +557,19 @@ export const Minting = ({
fullWidth
fullScreen
sx={{
"& .MuiDialog-paper": {
'& .MuiDialog-paper': {
margin: 0,
maxWidth: "100%",
width: "100%",
height: "100vh",
overflow: "hidden", // Prevent scrollbars
maxWidth: '100%',
width: '100%',
height: '100vh',
overflow: 'hidden', // Prevent scrollbars
},
}}
>
<DialogTitle id="alert-dialog-title">{"Manage your minting"}</DialogTitle>
<DialogTitle id="alert-dialog-title">{'Manage your minting'}</DialogTitle>
<IconButton
sx={{
position: "absolute",
position: 'absolute',
right: 8,
top: 8,
}}
@ -586,20 +581,20 @@ export const Minting = ({
</IconButton>
<DialogContent
sx={{
position: "relative",
position: 'relative',
}}
>
{isLoading && (
<Box
sx={{
position: "absolute",
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<FidgetSpinner
@ -614,8 +609,8 @@ export const Minting = ({
)}
<Card
sx={{
backgroundColor: "var(--bg-2)",
padding: "10px",
backgroundColor: 'var(--bg-2)',
padding: '10px',
}}
>
<Typography>Account: {handleNames(accountInfo?.address)}</Typography>
@ -631,11 +626,11 @@ export const Minting = ({
{isPartOfMintingGroup && !accountIsMinting && (
<Box
sx={{
display: "flex",
gap: "5px",
flexDirection: "column",
width: "100%",
alignItems: "center",
display: 'flex',
gap: '5px',
flexDirection: 'column',
width: '100%',
alignItems: 'center',
}}
>
<Button
@ -645,15 +640,15 @@ export const Minting = ({
}}
disabled={mintingAccounts?.length > 1}
sx={{
backgroundColor: "var(--green)",
color: "black",
fontWeight: "bold",
backgroundColor: 'var(--green)',
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
maxWidth: "90%",
width: "200px",
"&:hover": {
backgroundColor: "var(--green)",
color: "black",
maxWidth: '90%',
width: '200px',
'&:hover': {
backgroundColor: 'var(--green)',
color: 'black',
opacity: 1,
},
}}
@ -675,16 +670,16 @@ export const Minting = ({
)}
<Card
sx={{
backgroundColor: "var(--bg-2)",
padding: "10px",
backgroundColor: 'var(--bg-2)',
padding: '10px',
}}
>
{accountIsMinting && (
<Box
sx={{
display: "flex",
gap: "5px",
flexDirection: "column",
display: 'flex',
gap: '5px',
flexDirection: 'column',
}}
>
<Typography>
@ -698,9 +693,9 @@ export const Minting = ({
<Box
key={acct?.mintingAccount}
sx={{
display: "flex",
gap: "10px",
flexDirection: "column",
display: 'flex',
gap: '10px',
flexDirection: 'column',
}}
>
<Typography>
@ -709,15 +704,15 @@ export const Minting = ({
<Button
size="small"
sx={{
backgroundColor: "var(--danger)",
color: "black",
fontWeight: "bold",
backgroundColor: 'var(--danger)',
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
maxWidth: "90%",
width: "200px",
"&:hover": {
backgroundColor: "var(--danger)",
color: "black",
maxWidth: '90%',
width: '200px',
'&:hover': {
backgroundColor: 'var(--danger)',
color: 'black',
opacity: 1,
},
}}
@ -745,17 +740,17 @@ export const Minting = ({
{!isPartOfMintingGroup && (
<Card
sx={{
backgroundColor: "var(--bg-2)",
padding: "10px",
backgroundColor: 'var(--bg-2)',
padding: '10px',
}}
>
<Box
sx={{
display: "flex",
gap: "5px",
flexDirection: "column",
width: "100%",
alignItems: "center",
display: 'flex',
gap: '5px',
flexDirection: 'column',
width: '100%',
alignItems: 'center',
}}
>
<Typography>
@ -768,22 +763,22 @@ export const Minting = ({
<Button
size="small"
sx={{
backgroundColor: "var(--green)",
color: "black",
fontWeight: "bold",
backgroundColor: 'var(--green)',
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
"&:hover": {
backgroundColor: "var(--green)",
color: "black",
'&:hover': {
backgroundColor: 'var(--green)',
color: 'black',
opacity: 1,
},
}}
onClick={() => {
executeEvent("addTab", {
data: { service: "APP", name: "q-mintership" },
executeEvent('addTab', {
data: { service: 'APP', name: 'q-mintership' },
});
executeEvent("open-apps-mode", {});
executeEvent('open-apps-mode', {});
setIsOpenMinting(false);
}}
variant="contained"
@ -801,12 +796,13 @@ export const Minting = ({
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{isShowNext ? "Confirmed" : "Please Wait"}
{isShowNext ? 'Confirmed' : 'Please Wait'}
</DialogTitle>
<DialogContent>
{!isShowNext && (
<Typography>
Confirming creation of rewardshare on chain. Please be patient, this could take up to 90 seconds.
Confirming creation of rewardshare on chain. Please be
patient, this could take up to 90 seconds.
</Typography>
)}
{isShowNext && (
@ -814,16 +810,18 @@ export const Minting = ({
Rewardshare confirmed. Please click Next.
</Typography>
)}
</DialogContent>
<DialogActions>
<Button disabled={!isShowNext} variant="contained" onClick={onOk} autoFocus>
<Button
disabled={!isShowNext}
variant="contained"
onClick={onOk}
autoFocus
>
Next
</Button>
</DialogActions>
</Dialog>
)}
</DialogContent>
@ -837,7 +835,7 @@ export const Minting = ({
</Button>
</DialogActions>
<Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={openSnack}
autoHideDuration={6000}
onClose={handleClose}
@ -846,7 +844,7 @@ export const Minting = ({
onClose={handleClose}
severity={info?.type}
variant="filled"
sx={{ width: "100%" }}
sx={{ width: '100%' }}
>
{info?.message}
</Alert>

View File

@ -4,56 +4,59 @@ import {
TextField,
TextFieldProps,
styled,
} from "@mui/material";
import { forwardRef, useState } from "react";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import VisibilityIcon from "@mui/icons-material/Visibility";
useTheme,
} from '@mui/material';
import { forwardRef, useState } from 'react';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import VisibilityIcon from '@mui/icons-material/Visibility';
export const CustomInput = styled(TextField)(({ theme }) => ({
width: "183px",
borderRadius: "5px",
width: '183px',
borderRadius: '5px',
backgroundColor: theme.palette.background.paper,
outline: "none",
outline: 'none',
input: {
fontSize: 10,
fontFamily: "Inter",
fontFamily: 'Inter',
fontWeight: 400,
color: theme.palette.text.primary,
"&::placeholder": {
'&::placeholder': {
fontSize: 16,
color: theme.palette.text.disabled,
},
outline: "none",
padding: "10px",
outline: 'none',
padding: '10px',
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
'& .MuiOutlinedInput-root': {
'& fieldset': {
border: `0.5px solid ${theme.palette.divider}`,
},
"&:hover fieldset": {
'&:hover fieldset': {
border: `0.5px solid ${theme.palette.divider}`,
},
"&.Mui-focused fieldset": {
'&.Mui-focused fieldset': {
border: `0.5px solid ${theme.palette.divider}`,
},
},
"& .MuiInput-underline:before": {
borderBottom: "none",
'& .MuiInput-underline:before': {
borderBottom: 'none',
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before": {
borderBottom: "none",
'& .MuiInput-underline:hover:not(.Mui-disabled):before': {
borderBottom: 'none',
},
"& .MuiInput-underline:after": {
borderBottom: "none",
'& .MuiInput-underline:after': {
borderBottom: 'none',
},
}));
}));
export const PasswordField = forwardRef<HTMLInputElement, TextFieldProps>(
({ ...props }, ref) => {
const [canViewPassword, setCanViewPassword] = useState(false);
const theme = useTheme();
return (
<CustomInput
type={canViewPassword ? "text" : "password"}
type={canViewPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment
@ -68,22 +71,14 @@ export const PasswordField = forwardRef<HTMLInputElement, TextFieldProps>(
data-testid="plain-text-indicator"
sx={{ minWidth: 0, p: 0 }}
>
<VisibilityOffIcon
sx={{
color: "white",
}}
/>
<VisibilityOffIcon />
</ButtonBase>
) : (
<ButtonBase
data-testid="password-text-indicator"
sx={{ minWidth: 0, p: 0 }}
>
<VisibilityIcon
sx={{
color: "white",
}}
/>
<VisibilityIcon />
</ButtonBase>
)}
</InputAdornment>

View File

@ -1,31 +1,43 @@
import React, { useMemo } from 'react'
import QMailLogo from '../assets/QMailLogo.png'
import { useRecoilState } from 'recoil'
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global'
import { isLessThanOneWeekOld } from './Group/QMailMessages'
import { ButtonBase, Tooltip } from '@mui/material'
import { executeEvent } from '../utils/events'
export const QMailStatus = () => {
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(qMailLastEnteredTimestampAtom)
const [mails, setMails] = useRecoilState(mailsAtom)
import { useMemo } from 'react';
import QMailLogo from '../assets/QMailLogo.png';
import { useRecoilState } from 'recoil';
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global';
import { isLessThanOneWeekOld } from './Group/QMailMessages';
import { ButtonBase, Tooltip } from '@mui/material';
import { executeEvent } from '../utils/events';
const hasNewMail = useMemo(()=> {
if(mails?.length === 0) return false
const latestMail = mails[0]
if(!lastEnteredTimestamp && isLessThanOneWeekOld(latestMail?.created)) return true
if((lastEnteredTimestamp < latestMail?.created) && isLessThanOneWeekOld(latestMail?.created)) return true
return false
}, [lastEnteredTimestamp, mails])
export const QMailStatus = () => {
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState(
qMailLastEnteredTimestampAtom
);
const [mails, setMails] = useRecoilState(mailsAtom);
const hasNewMail = useMemo(() => {
if (mails?.length === 0) return false;
const latestMail = mails[0];
if (!lastEnteredTimestamp && isLessThanOneWeekOld(latestMail?.created))
return true;
if (
lastEnteredTimestamp < latestMail?.created &&
isLessThanOneWeekOld(latestMail?.created)
)
return true;
return false;
}, [lastEnteredTimestamp, mails]);
return (
<ButtonBase onClick={()=> {
executeEvent("addTab", { data: { service: 'APP', name: 'q-mail' } });
executeEvent("open-apps-mode", { });
setLastEnteredTimestamp(Date.now())
}} style={{
position: 'relative'
}}>
<ButtonBase
onClick={() => {
executeEvent('addTab', { data: { service: 'APP', name: 'q-mail' } });
executeEvent('open-apps-mode', {});
setLastEnteredTimestamp(Date.now());
}}
style={{
position: 'relative',
}}
>
{hasNewMail && (
<div style={{
<div
style={{
position: 'absolute',
zIndex: 1,
top: '-7px',
@ -34,24 +46,29 @@ export const QMailStatus = () => {
height: '15px',
width: '15px',
borderRadius: '50%',
outline: '1px solid white'
}} />
outline: '1px solid white',
}}
/>
)}
<Tooltip
title={<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>Q-MAIL</span>}
title={
<span style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}>
Q-MAIL
</span>
}
placement="left"
arrow
sx={{ fontSize: "24" }}
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
color: '#ffffff',
backgroundColor: '#444444',
},
},
arrow: {
sx: {
color: "#444444",
color: '#444444',
},
},
}}
@ -59,5 +76,5 @@ export const QMailStatus = () => {
<img style={{ width: '24px', height: 'auto' }} src={QMailLogo} />
</Tooltip>
</ButtonBase>
)
}
);
};

View File

@ -1,47 +1,50 @@
import { Box, CircularProgress } from '@mui/material';
import React, { useEffect, useState } from 'react'
import { CustomButton, CustomInput, CustomLabel, TextP } from '../App-styles';
import React, { useEffect, useState } from 'react';
import {
CustomButton,
CustomInput,
CustomLabel,
TextP,
} from '../styles/App-styles';
import { Spacer } from '../common/Spacer';
import BoundedNumericTextField from '../common/BoundedNumericTextField';
import { PasswordField } from './PasswordField/PasswordField';
import { ErrorText } from './ErrorText/ErrorText';
import { getFee } from '../background';
export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
const [paymentTo, setPaymentTo] = useState<string>(defaultPaymentTo);
const [paymentAmount, setPaymentAmount] = useState<number>(0);
const [paymentPassword, setPaymentPassword] = useState<string>("");
const [sendPaymentError, setSendPaymentError] = useState<string>("");
const [sendPaymentSuccess, setSendPaymentSuccess] = useState<string>("");
const [paymentPassword, setPaymentPassword] = useState<string>('');
const [sendPaymentError, setSendPaymentError] = useState<string>('');
const [sendPaymentSuccess, setSendPaymentSuccess] = useState<string>('');
const [isLoadingSendCoin, setIsLoadingSendCoin] = useState<boolean>(false);
const sendCoinFunc = async() => {
const sendCoinFunc = async () => {
try {
setSendPaymentError("");
setSendPaymentSuccess("");
setSendPaymentError('');
setSendPaymentSuccess('');
if (!paymentTo) {
setSendPaymentError("Please enter a recipient");
setSendPaymentError('Please enter a recipient');
return;
}
if (!paymentAmount) {
setSendPaymentError("Please enter an amount greater than 0");
setSendPaymentError('Please enter an amount greater than 0');
return;
}
if (!paymentPassword) {
setSendPaymentError("Please enter your wallet password");
setSendPaymentError('Please enter your wallet password');
return;
}
const fee = await getFee('PAYMENT')
const fee = await getFee('PAYMENT');
await show({
message: `Would you like to transfer ${Number(paymentAmount)} QORT?` ,
paymentFee: fee.fee + ' QORT'
})
message: `Would you like to transfer ${Number(paymentAmount)} QORT?`,
paymentFee: fee.fee + ' QORT',
});
setIsLoadingSendCoin(true);
window
.sendMessage("sendCoin", {
.sendMessage('sendCoin', {
amount: Number(paymentAmount),
receiver: paymentTo.trim(),
password: paymentPassword,
@ -50,13 +53,12 @@ export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
if (response?.error) {
setSendPaymentError(response.error);
} else {
onSuccess()
onSuccess();
}
setIsLoadingSendCoin(false);
})
.catch((error) => {
console.error("Failed to send coin:", error);
console.error('Failed to send coin:', error);
setIsLoadingSendCoin(false);
});
} catch (error) {
@ -67,16 +69,16 @@ export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
<>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<TextP
sx={{
textAlign: "start",
lineHeight: "24px",
fontSize: "20px",
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 600,
}}
>
@ -85,20 +87,20 @@ export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
<Spacer height="35px" />
<TextP
sx={{
textAlign: "start",
lineHeight: "16px",
fontSize: "20px",
textAlign: 'start',
lineHeight: '16px',
fontSize: '20px',
fontWeight: 600,
color: "rgba(255, 255, 255, 0.5)",
color: 'rgba(255, 255, 255, 0.5)',
}}
>
Balance:
</TextP>
<TextP
sx={{
textAlign: "start",
lineHeight: "24px",
fontSize: "20px",
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 700,
}}
>
@ -117,9 +119,7 @@ export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
autoComplete="off"
/>
<Spacer height="6px" />
<CustomLabel htmlFor="standard-adornment-amount">
Amount
</CustomLabel>
<CustomLabel htmlFor="standard-adornment-amount">Amount</CustomLabel>
<Spacer height="5px" />
<BoundedNumericTextField
value={paymentAmount}
@ -148,20 +148,23 @@ export const QortPayment = ({balance, show, onSuccess, defaultPaymentTo}) => {
<Spacer height="25px" />
<CustomButton
sx={{
cursor: isLoadingSendCoin ? 'default' : 'pointer'
cursor: isLoadingSendCoin ? 'default' : 'pointer',
}}
onClick={() => {
if(isLoadingSendCoin) return
if (isLoadingSendCoin) return;
sendCoinFunc();
}}
>
{isLoadingSendCoin && (
<CircularProgress size={16} sx={{
color: 'white'
}} />
<CircularProgress
size={16}
sx={{
color: 'white',
}}
/>
)}
Send
</CustomButton>
</>
)
}
);
};

View File

@ -1,24 +1,19 @@
import { createContext, useContext, useState, useMemo } from 'react';
import { createTheme, ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { darkTheme, lightTheme } from '../../styles/theme';
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
const ThemeContext = createContext({
themeMode: 'light',
toggleTheme: () => {},
});
const lightTheme = createTheme({
palette: {
mode: 'light',
},
});
const ThemeContext = createContext({ themeMode: 'light', toggleTheme: () => {} });
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [themeMode, setThemeMode] = useState('light');
const theme = useMemo(() => (themeMode === 'light' ? lightTheme : darkTheme), [themeMode]);
const theme = useMemo(
() => (themeMode === 'light' ? lightTheme : darkTheme),
[themeMode]
);
const toggleTheme = () => {
setThemeMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));

View File

@ -1,58 +1,58 @@
import { useThemeContext } from "./ThemeContext";
import { styled, Switch } from "@mui/material";
import { useThemeContext } from './ThemeContext';
import { styled, Switch } from '@mui/material';
const ThemeSwitch = styled(Switch)(({ theme }) => ({
width: 62,
height: 34,
padding: 7,
"& .MuiSwitch-switchBase": {
'& .MuiSwitch-switchBase': {
margin: 1,
padding: 0,
transform: "translateX(6px)",
"&.Mui-checked": {
color: "#fff",
transform: "translateX(22px)",
"& .MuiSwitch-thumb:before": {
transform: 'translateX(6px)',
'&.Mui-checked': {
color: '#fff',
transform: 'translateX(22px)',
'& .MuiSwitch-thumb:before': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
"#fff"
'#fff'
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
},
"& + .MuiSwitch-track": {
'& + .MuiSwitch-track': {
opacity: 1,
backgroundColor: "#aab4be",
...theme.applyStyles("dark", {
backgroundColor: "#8796A5",
backgroundColor: '#aab4be',
...theme.applyStyles('dark', {
backgroundColor: '#8796A5',
}),
},
},
},
"& .MuiSwitch-thumb": {
backgroundColor: "#001e3c",
'& .MuiSwitch-thumb': {
backgroundColor: '#fde402',
width: 32,
height: 32,
"&::before": {
'&::before': {
content: "''",
position: "absolute",
width: "100%",
height: "100%",
position: 'absolute',
width: '100%',
height: '100%',
left: 0,
top: 0,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
"#fff"
'#000000'
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
},
...theme.applyStyles("dark", {
backgroundColor: "#003892",
...theme.applyStyles('dark', {
backgroundColor: '#003892',
}),
},
"& .MuiSwitch-track": {
'& .MuiSwitch-track': {
opacity: 1,
backgroundColor: "#aab4be",
backgroundColor: '#aab4be',
borderRadius: 20 / 2,
...theme.applyStyles("dark", {
backgroundColor: "#8796A5",
...theme.applyStyles('dark', {
backgroundColor: '#8796A5',
}),
},
}));
@ -62,14 +62,14 @@ const ThemeSelector = ({ style }) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1px",
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '1px',
...style,
}}
>
<ThemeSwitch checked={themeMode === "dark"} onChange={toggleTheme} />
<ThemeSwitch checked={themeMode === 'dark'} onChange={toggleTheme} />
</div>
);
};

View File

@ -169,12 +169,15 @@ useEffect(()=> {
onClick={async () => {
try {
setIsLoading(true)
if(isAlreadyBlocked === true){
await removeBlockFromList(address, name)
} else if(isAlreadyBlocked === false) {
await addToBlockList(address, name)
}
executeEvent('updateChatMessagesWithBlocks', true)
executeEvent("blockUserFromOutside", {
user: address
})
// if(isAlreadyBlocked === true){
// await removeBlockFromList(address, name)
// } else if(isAlreadyBlocked === false) {
// await addToBlockList(address, name)
// }
// executeEvent('updateChatMessagesWithBlocks', true)
} catch (error) {
console.error(error)
} finally {

View File

@ -1,13 +1,12 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import "./messaging/messagesToBackground";
import { MessageQueueProvider } from "./MessageQueueContext.tsx";
import { RecoilRoot } from "recoil";
import { ThemeProvider } from "./components/Theme/ThemeContext.tsx";
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import '../src/styles/index.css';
import './messaging/messagesToBackground';
import { MessageQueueProvider } from './MessageQueueContext.tsx';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
ReactDOM.createRoot(document.getElementById("root")!).render(
ReactDOM.createRoot(document.getElementById('root')!).render(
<>
<ThemeProvider>
<MessageQueueProvider>

260
src/styles/App-styles.ts Normal file
View File

@ -0,0 +1,260 @@
import { Typography, Box, TextField, InputLabel } from '@mui/material';
import { styled } from '@mui/system';
export const AppContainer = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
width: '100vw',
height: '100vh',
radius: '15px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
overflow: 'hidden',
}));
export const AuthenticatedContainer = styled(Box)(({ theme }) => ({
display: 'flex',
width: '100%',
height: '100%',
justifyContent: 'space-between',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerLeft = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
height: '100%',
width: '100%',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerRight = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
width: '60px',
height: '100%',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AuthenticatedContainerInnerTop = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%px',
height: '60px',
padding: '20px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextP = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 600,
fontFamily: 'Inter',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextItalic = styled('span')(({ theme }) => ({
fontSize: '13px',
fontWeight: 600,
fontFamily: 'Inter',
fontStyle: 'italic',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const TextSpan = styled('span')(({ theme }) => ({
fontSize: '13px',
fontFamily: 'Inter',
fontWeight: 800,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AddressBox = styled(Box)(({ theme }) => ({
display: 'flex',
border: `1px solid ${
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.3)'
}`,
justifyContent: 'space-between',
alignItems: 'center',
width: 'auto',
height: '25px',
padding: '5px 15px',
gap: '5px',
borderRadius: '100px',
fontFamily: 'Inter',
fontSize: '12px',
fontWeight: 600,
lineHeight: '14.52px',
textAlign: 'left',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
cursor: 'pointer',
transition: 'all 0.2s',
'&:hover': {
backgroundColor:
theme.palette.mode === 'dark'
? 'rgba(41, 41, 43, 1)'
: 'rgba(240, 240, 240, 1)',
color: theme.palette.mode === 'dark' ? '#fff' : '#000',
'svg path': {
fill: theme.palette.mode === 'dark' ? '#fff' : '#000',
},
},
}));
export const CustomButton = styled(Box)(({ theme }) => ({
boxSizing: 'border-box',
padding: '15px 20px',
gap: '10px',
border: `0.5px solid ${
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.3)'
}`,
filter: 'drop-shadow(1px 4px 10.5px rgba(0, 0, 0, 0.3))',
borderRadius: '5px',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
width: 'fit-content',
minWidth: '160px',
cursor: 'pointer',
transition: 'all 0.2s',
fontWeight: 600,
fontFamily: 'Inter',
textAlign: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
'&:hover': {
backgroundColor:
theme.palette.mode === 'dark'
? 'rgba(41, 41, 43, 1)'
: 'rgba(230, 230, 230, 1)',
color: '#fff',
'svg path': {
fill: '#fff',
},
},
}));
interface CustomButtonProps {
bgColor?: string;
color?: string;
}
export const CustomButtonAccept = styled(Box)<CustomButtonProps>(
({ bgColor, color, theme }) => ({
boxSizing: 'border-box',
padding: '15px 20px',
gap: '10px',
border: `0.5px solid ${
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.3)'
}`,
filter: 'drop-shadow(1px 4px 10.5px rgba(0,0,0,0.3))',
borderRadius: 5,
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
width: 'fit-content',
transition: 'all 0.2s',
minWidth: 160,
cursor: 'pointer',
fontWeight: 600,
fontFamily: 'Inter',
textAlign: 'center',
opacity: 0.7,
// Color and backgroundColor with fallbacks
backgroundColor:
bgColor || (theme.palette.mode === 'dark' ? '#1d1d1d' : '#f5f5f5'),
color: color || (theme.palette.mode === 'dark' ? '#fff' : '#000'),
'&:hover': {
opacity: 1,
backgroundColor:
bgColor ||
(theme.palette.mode === 'dark'
? 'rgba(41, 41, 43, 1)'
: 'rgba(230, 230, 230, 1)'),
color: color || '#fff',
svg: {
path: {
fill: color || '#fff',
},
},
},
})
);
export const CustomInput = styled(TextField)(({ theme }) => ({
width: '183px', // Adjust the width as needed
borderRadius: '5px',
// backgroundColor: "rgba(30, 30, 32, 1)",
outline: 'none',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
input: {
fontSize: 10,
fontFamily: 'Inter',
fontWeight: 400,
color: 'white',
'&::placeholder': {
fontSize: 16,
color: 'rgba(255, 255, 255, 0.2)',
},
outline: 'none',
padding: '10px',
},
'& .MuiOutlinedInput-root': {
'& fieldset': {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
'&:hover fieldset': {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
'&.Mui-focused fieldset': {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
},
'& .MuiInput-underline:before': {
borderBottom: 'none',
},
'& .MuiInput-underline:hover:not(.Mui-disabled):before': {
borderBottom: 'none',
},
'& .MuiInput-underline:after': {
borderBottom: 'none',
},
}));
export const CustomLabel = styled(InputLabel)(({ theme }) => ({
fontWeight: 400,
fontFamily: 'Inter',
fontSize: '10px',
lineHeight: '12px',
color:
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.5)',
}));

View File

@ -9,25 +9,32 @@
}
.tooltip .bottom {
min-width: 225px;
max-width: 250px;
top: 35px;
right: 0px;
/* transform: translate(-50%, 0); */
padding: 10px 10px;
color: var(--black);
background-color: var(--bg-2);
font-weight: normal;
font-size: 13px;
border-radius: 8px;
position: absolute;
z-index: 99999999;
box-sizing: border-box;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
border: 1px solid var(--black);
visibility: hidden;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
box-sizing: border-box;
font-size: 13px;
font-weight: normal;
max-width: 250px;
min-width: 225px;
opacity: 0;
padding: 10px 10px;
position: absolute;
right: 0px;
top: 35px;
transition: opacity 0.2s;
visibility: hidden;
z-index: 99999999;
}
.tooltip[data-theme='light'] .bottom {
background-color: #f1f1f1;
color: #000000;
}
.tooltip[data-theme='dark'] .bottom {
background-color: var(--bg-2);
color: var(--black);
}
.tooltip:hover .bottom {
@ -47,13 +54,13 @@
}
.tooltip .bottom i::after {
content: "";
position: absolute;
width: 12px;
height: 12px;
left: 50%;
transform: translate(-50%, 50%) rotate(45deg);
background-color: var(--white);
border: 1px solid var(--black);
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
content: '';
height: 12px;
left: 50%;
position: absolute;
transform: translate(-50%, 50%) rotate(45deg);
width: 12px;
}

View File

@ -1,21 +1,21 @@
@font-face {
font-family: "Inter";
src: url("./styles/fonts/Inter-SemiBold.ttf") format("truetype");
font-family: 'Inter';
src: url('./styles/fonts/Inter-SemiBold.ttf') format('truetype');
font-weight: 600;
}
@font-face {
font-family: "Inter";
src: url("./styles/fonts/Inter-ExtraBold.ttf") format("truetype");
font-family: 'Inter';
src: url('./styles/fonts/Inter-ExtraBold.ttf') format('truetype');
font-weight: 800;
}
@font-face {
font-family: "Inter";
src: url("./styles/fonts/Inter-Bold.ttf") format("truetype");
font-family: 'Inter';
src: url('./styles/fonts/Inter-Bold.ttf') format('truetype');
font-weight: 700;
}
@font-face {
font-family: "Inter";
src: url("./styles/fonts/Inter-Regular.ttf") format("truetype");
font-family: 'Inter';
src: url('./styles/fonts/Inter-Regular.ttf') format('truetype');
font-weight: 400;
}
@ -89,7 +89,7 @@ body {
}
@property --var1 {
syntax: "<color>";
syntax: '<color>';
inherits: true;
initial-value: transparent;
}

View File

@ -1,54 +1,50 @@
import { createTheme } from '@mui/material/styles'
import { createTheme } from '@mui/material/styles';
// Extend the Theme interface
const commonThemeOptions = {
typography: {
fontFamily: [
'Roboto'
].join(','),
fontFamily: ['Roboto'].join(','),
h1: {
fontSize: '2rem',
fontWeight: 600
fontWeight: 600,
},
h2: {
fontSize: '1.75rem',
fontWeight: 500
fontWeight: 500,
},
h3: {
fontSize: '1.5rem',
fontWeight: 500
fontWeight: 500,
},
h4: {
fontSize: '1.25rem',
fontWeight: 500
fontWeight: 500,
},
h5: {
fontSize: '1rem',
fontWeight: 500
fontWeight: 500,
},
h6: {
fontSize: '0.875rem',
fontWeight: 500
fontWeight: 500,
},
body1: {
fontSize: '23px',
fontWeight: 400,
lineHeight: 1.5,
letterSpacing: '0.5px'
letterSpacing: 'normal',
},
body2: {
fontSize: '18px',
fontWeight: 400,
lineHeight: 1.4,
letterSpacing: '0.2px'
}
letterSpacing: '0.2px',
},
},
spacing: 8,
shape: {
borderRadius: 4
borderRadius: 4,
},
breakpoints: {
values: {
@ -56,8 +52,8 @@ const commonThemeOptions = {
sm: 600,
md: 900,
lg: 1200,
xl: 1536
}
xl: 1536,
},
},
components: {
MuiButton: {
@ -66,25 +62,24 @@ const commonThemeOptions = {
backgroundColor: 'inherit',
transition: 'filter 0.3s ease-in-out',
'&:hover': {
filter: 'brightness(1.1)'
}
}
filter: 'brightness(1.1)',
},
},
},
defaultProps: {
disableElevation: true,
disableRipple: true
}
disableRipple: true,
},
},
MuiModal: {
styleOverrides: {
root: {
zIndex: 50000,
},
}
}
}
}
},
},
},
};
const lightTheme = createTheme({
...commonThemeOptions,
@ -93,46 +88,46 @@ const lightTheme = createTheme({
primary: {
main: '#f4f4fb',
dark: '#eaecf4',
light: '#f9f9fd'
light: '#f9f9fd',
},
secondary: {
main: '#1EAAF1'
main: '#c2deec',
},
background: {
default: '#fafafa',
paper: '#f0f0f0'
paper: '#f0f0f0',
},
text: {
primary: '#000000',
secondary: '#525252'
}
secondary: '#525252',
},
},
components: {
MuiCard: {
styleOverrides: {
root: {
boxShadow:
'rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;',
'rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(230, 200, 200, 0.06) 0px 1px 2px 0px;',
borderRadius: '8px',
transition: 'all 0.3s ease-in-out',
'&:hover': {
cursor: 'pointer',
boxShadow:
'rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;'
}
}
}
'rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;',
},
},
},
},
MuiIcon: {
defaultProps: {
style: {
color: '#000000'
}
}
}
color: '#000000',
opacity: 0.5,
},
})
},
},
},
});
const darkTheme = createTheme({
...commonThemeOptions,
@ -140,45 +135,46 @@ const darkTheme = createTheme({
mode: 'dark',
primary: {
main: '#2e3d60',
dark: "#1a2744",
light: "#3f4b66",
dark: '#1a2744',
light: '#3f4b66',
},
secondary: {
main: '#45adff'
main: '#45adff',
},
background: {
default: '#313338',
paper: "#1e1e20"
paper: '#1e1e20',
},
text: {
primary: '#ffffff',
secondary: '#b3b3b3'
}
secondary: '#b3b3b3',
},
},
components: {
MuiCard: {
styleOverrides: {
root: {
boxShadow: "none",
boxShadow: 'none',
borderRadius: '8px',
transition: 'all 0.3s ease-in-out',
'&:hover': {
cursor: 'pointer',
boxShadow:
' 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);'
}
}
}
' 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);',
},
},
},
},
MuiIcon: {
defaultProps: {
style: {
color: '#ffffff'
}
}
}
color: '#ffffff',
opacity: 0.5,
},
})
},
},
},
});
export { lightTheme, darkTheme }
export { lightTheme, darkTheme };