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

View File

@ -1,11 +1,11 @@
import React from "react"; import React from 'react';
import { styled } from "@mui/system"; import { styled } from '@mui/system';
import { SVGProps } from "./interfaces"; import { SVGProps } from './interfaces';
// Create a styled container with hover effects // Create a styled container with hover effects
const SvgContainer = styled("svg")({ const SvgContainer = styled('svg')({
"& path": { '& path': {
fill: "rgba(41, 41, 43, 1)", // Default to red if no color prop 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 <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" 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> </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 theme = useTheme();
const setColor = color ? color : theme.palette.text.primary const setColor = color ? color : theme.palette.text.primary;
return ( return (
<svg <svg
{...children}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@ -6,14 +6,23 @@ import { SVGProps } from './interfaces';
const SvgContainer = styled('svg')({ const SvgContainer = styled('svg')({
'& path': { '& path': {
fill: 'rgba(41, 41, 43, 1)', // Default to red if no color prop 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 ( return (
<SvgContainer width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <SvgContainer
<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" /> 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> </SvgContainer>
); );
}; };

View File

@ -1,16 +1,16 @@
import React from 'react';
export const StarEmptyIcon = () => { export const StarEmptyIcon = () => {
return ( return (
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg"> <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"/> width="12"
</svg> 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 = () => { export const StarFilledIcon = () => {
return ( return (
<svg <svg

View File

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

View File

@ -1,180 +1,195 @@
import { atom, selectorFamily } from 'recoil'; import { atom, selectorFamily } from 'recoil';
export const sortablePinnedAppsAtom = atom({ export const sortablePinnedAppsAtom = atom({
key: 'sortablePinnedAppsFromAtom', key: 'sortablePinnedAppsFromAtom',
default: [{ default: [
name: 'Q-Tube', {
service: 'APP' name: 'Q-Tube',
}, { service: 'APP',
name: 'Q-Mail', },
service: 'APP' {
}, { name: 'Q-Mail',
name: 'Q-Share', service: 'APP',
service: 'APP' },
}, { {
name: 'Q-Fund', name: 'Q-Share',
service: 'APP' service: 'APP',
}, { },
name: 'Q-Shop', {
service: 'APP' name: 'Q-Fund',
}, service: 'APP',
{ },
name: 'Q-Trade', {
service: 'APP' name: 'Q-Shop',
}, service: 'APP',
{ },
name: 'Q-Support', {
service: 'APP' name: 'Q-Trade',
}, service: 'APP',
{ },
name: 'Q-Manager', {
service: 'APP' name: 'Q-Support',
}, service: 'APP',
{ },
name: 'Q-Blog', {
service: 'APP' name: 'Q-Manager',
}, service: 'APP',
{ },
name: 'Q-Mintership', {
service: 'APP' name: 'Q-Blog',
}, service: 'APP',
{ },
name: 'Q-Wallets', {
service: 'APP' name: 'Q-Mintership',
}, service: 'APP',
{ },
name: 'Q-Search', {
service: 'APP' name: 'Q-Wallets',
}, service: 'APP',
], },
{
name: 'Q-Search',
service: 'APP',
},
],
}); });
export const canSaveSettingToQdnAtom = atom({ export const canSaveSettingToQdnAtom = atom({
key: 'canSaveSettingToQdnAtom', key: 'canSaveSettingToQdnAtom',
default: false, default: false,
}); });
export const settingsQDNLastUpdatedAtom = atom({ export const settingsQDNLastUpdatedAtom = atom({
key: 'settingsQDNLastUpdatedAtom', key: 'settingsQDNLastUpdatedAtom',
default: -100, default: -100,
}); });
export const settingsLocalLastUpdatedAtom = atom({ export const settingsLocalLastUpdatedAtom = atom({
key: 'settingsLocalLastUpdatedAtom', key: 'settingsLocalLastUpdatedAtom',
default: 0, default: 0,
}); });
export const oldPinnedAppsAtom = atom({ export const oldPinnedAppsAtom = atom({
key: 'oldPinnedAppsAtom', key: 'oldPinnedAppsAtom',
default: [], default: [],
});
export const isUsingImportExportSettingsAtom = atom({
key: 'isUsingImportExportSettingsAtom',
default: null,
}); });
export const isUsingImportExportSettingsAtom = atom({
key: 'isUsingImportExportSettingsAtom',
default: null,
});
export const fullScreenAtom = atom({ export const fullScreenAtom = atom({
key: 'fullScreenAtom', key: 'fullScreenAtom',
default: false, default: false,
}); });
export const hasSettingsChangedAtom = atom({ export const hasSettingsChangedAtom = atom({
key: 'hasSettingsChangedAtom', key: 'hasSettingsChangedAtom',
default: false, default: false,
}); });
export const navigationControllerAtom = atom({ export const navigationControllerAtom = atom({
key: 'navigationControllerAtom', key: 'navigationControllerAtom',
default: {}, default: {},
}); });
export const enabledDevModeAtom = atom({ export const enabledDevModeAtom = atom({
key: 'enabledDevModeAtom', key: 'enabledDevModeAtom',
default: false, default: false,
}); });
export const myGroupsWhereIAmAdminAtom = atom({ export const myGroupsWhereIAmAdminAtom = atom({
key: 'myGroupsWhereIAmAdminAtom', key: 'myGroupsWhereIAmAdminAtom',
default: [], default: [],
}); });
export const promotionTimeIntervalAtom = atom({ export const promotionTimeIntervalAtom = atom({
key: 'promotionTimeIntervalAtom', key: 'promotionTimeIntervalAtom',
default: 0, default: 0,
}); });
export const promotionsAtom = atom({ export const promotionsAtom = atom({
key: 'promotionsAtom', key: 'promotionsAtom',
default: [], default: [],
}); });
export const resourceDownloadControllerAtom = atom({ export const resourceDownloadControllerAtom = atom({
key: 'resourceDownloadControllerAtom', key: 'resourceDownloadControllerAtom',
default: {}, default: {},
}); });
export const resourceKeySelector = selectorFamily({ export const resourceKeySelector = selectorFamily({
key: 'resourceKeySelector', key: 'resourceKeySelector',
get: (key) => ({ get }) => { get:
const resources = get(resourceDownloadControllerAtom); (key) =>
return resources[key] || null; // Return the value for the key or null if not found ({ get }) => {
}, const resources = get(resourceDownloadControllerAtom);
return resources[key] || null; // Return the value for the key or null if not found
},
}); });
export const blobControllerAtom = atom({ export const blobControllerAtom = atom({
key: 'blobControllerAtom', key: 'blobControllerAtom',
default: {}, default: {},
}); });
export const blobKeySelector = selectorFamily({ export const blobKeySelector = selectorFamily({
key: 'blobKeySelector', key: 'blobKeySelector',
get: (key) => ({ get }) => { get:
const blobs = get(blobControllerAtom); (key) =>
return blobs[key] || null; // Return the value for the key or null if not found ({ get }) => {
}, const blobs = get(blobControllerAtom);
return blobs[key] || null; // Return the value for the key or null if not found
},
}); });
export const selectedGroupIdAtom = atom({ export const selectedGroupIdAtom = atom({
key: 'selectedGroupIdAtom', key: 'selectedGroupIdAtom',
default: null, default: null,
}); });
export const addressInfoControllerAtom = atom({ export const addressInfoControllerAtom = atom({
key: 'addressInfoControllerAtom', key: 'addressInfoControllerAtom',
default: {}, default: {},
}); });
export const addressInfoKeySelector = selectorFamily({ export const addressInfoKeySelector = selectorFamily({
key: 'addressInfoKeySelector', key: 'addressInfoKeySelector',
get: (key) => ({ get }) => { get:
const userInfo = get(addressInfoControllerAtom); (key) =>
return userInfo[key] || null; // Return the value for the key or null if not found ({ get }) => {
}, const userInfo = get(addressInfoControllerAtom);
return userInfo[key] || null; // Return the value for the key or null if not found
},
}); });
export const isDisabledEditorEnterAtom = atom({ export const isDisabledEditorEnterAtom = atom({
key: 'isDisabledEditorEnterAtom', key: 'isDisabledEditorEnterAtom',
default: false, default: false,
}); });
export const qMailLastEnteredTimestampAtom = atom({ export const qMailLastEnteredTimestampAtom = atom({
key: 'qMailLastEnteredTimestampAtom', key: 'qMailLastEnteredTimestampAtom',
default: null, default: null,
}); });
export const lastPaymentSeenTimestampAtom = atom<null | number>({ export const lastPaymentSeenTimestampAtom = atom<null | number>({
key: 'lastPaymentSeenTimestampAtom', key: 'lastPaymentSeenTimestampAtom',
default: null, default: null,
}); });
export const mailsAtom = atom({ export const mailsAtom = atom({
key: 'mailsAtom', key: 'mailsAtom',
default: [], default: [],
}); });
export const groupsPropertiesAtom = atom({ export const groupsPropertiesAtom = atom({
key: 'groupsPropertiesAtom', key: 'groupsPropertiesAtom',
default: {}, default: {},
}); });
export const isOpenBlockedModalAtom = atom({
key: 'isOpenBlockedModalAtom',
default: false,
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,21 @@
import React from "react"; import { TabParent } from './Apps-styles';
import { TabParent } from "./Apps-styles"; import NavCloseTab from '../../assets/svgs/NavCloseTab.svg';
import NavCloseTab from "../../assets/svgs/NavCloseTab.svg"; import { getBaseApiReact } from '../../App';
import { getBaseApiReact } from "../../App"; import { Avatar, ButtonBase } from '@mui/material';
import { Avatar, ButtonBase } from "@mui/material"; import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import LogoSelected from "../../assets/svgs/LogoSelected.svg"; import { executeEvent } from '../../utils/events';
import { executeEvent } from "../../utils/events";
export const AppsDevModeTabComponent = ({ isSelected, app }) => { export const AppsDevModeTabComponent = ({ isSelected, app }) => {
return ( return (
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
if (isSelected) { if (isSelected) {
executeEvent("removeTabDevMode", { executeEvent('removeTabDevMode', {
data: app, data: app,
}); });
return; return;
} }
executeEvent("setSelectedTabDevMode", { executeEvent('setSelectedTabDevMode', {
data: app, data: app,
isDevMode: true, isDevMode: true,
}); });
@ -24,15 +23,15 @@ export const AppsDevModeTabComponent = ({ isSelected, app }) => {
> >
<TabParent <TabParent
sx={{ sx={{
border: isSelected && "1px solid #FFFFFF", border: isSelected && '1px solid #FFFFFF',
}} }}
> >
{isSelected && ( {isSelected && (
<img <img
style={{ style={{
position: "absolute", position: 'absolute',
top: "-5px", top: '-5px',
right: "-5px", right: '-5px',
zIndex: 1, zIndex: 1,
}} }}
src={NavCloseTab} src={NavCloseTab}
@ -40,23 +39,25 @@ export const AppsDevModeTabComponent = ({ isSelected, app }) => {
)} )}
<Avatar <Avatar
sx={{ sx={{
height: "28px", height: '28px',
width: "28px", width: '28px',
}} }}
alt="" alt=""
src={``} src={``}
> >
<img <img
style={{ style={{
width: "28px", width: '28px',
height: "auto", height: 'auto',
}} }}
src={ app?.customIcon ? app?.customIcon : src={
app?.service app?.customIcon
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ ? app?.customIcon
app?.name : app?.service
}/qortal_avatar?async=true` ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
: LogoSelected app?.name
}/qortal_avatar?async=true`
: LogoSelected
} }
alt="center-icon" alt="center-icon"
/> />

View File

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

View File

@ -1,21 +1,22 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
AppCircleLabel, AppCircleLabel,
AppLibrarySubTitle, AppLibrarySubTitle,
AppsContainer, AppsContainer,
} from "./Apps-styles"; } from './Apps-styles';
import { Box, ButtonBase, Input } from "@mui/material"; import { Box, ButtonBase, Input, useTheme } from '@mui/material';
import { Add } from "@mui/icons-material"; import { Add } from '@mui/icons-material';
import { isMobile } from "../../App"; import { isMobile } from '../../App';
import { executeEvent } from "../../utils/events"; import { executeEvent } from '../../utils/events';
import { Spacer } from "../../common/Spacer"; import { Spacer } from '../../common/Spacer';
import { SortablePinnedApps } from "./SortablePinnedApps"; import { SortablePinnedApps } from './SortablePinnedApps';
import { extractComponents } from "../Chat/MessageDisplay"; import { extractComponents } from '../Chat/MessageDisplay';
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
import { AppsPrivate } from "./AppsPrivate"; import { AppsPrivate } from './AppsPrivate';
import ThemeSelector from "../Theme/ThemeSelector"; import ThemeSelector from '../Theme/ThemeSelector';
export const AppsHomeDesktop = ({ export const AppsHomeDesktop = ({
setMode, setMode,
myApp, myApp,
@ -23,7 +24,8 @@ export const AppsHomeDesktop = ({
availableQapps, availableQapps,
myName, myName,
}) => { }) => {
const [qortalUrl, setQortalUrl] = useState(""); const [qortalUrl, setQortalUrl] = useState('');
const theme = useTheme();
const openQortalUrl = () => { const openQortalUrl = () => {
try { try {
@ -31,22 +33,24 @@ export const AppsHomeDesktop = ({
const res = extractComponents(qortalUrl); const res = extractComponents(qortalUrl);
if (res) { if (res) {
const { service, name, identifier, path } = res; const { service, name, identifier, path } = res;
executeEvent("addTab", { data: { service, name, identifier, path } }); executeEvent('addTab', { data: { service, name, identifier, path } });
executeEvent("open-apps-mode", {}); executeEvent('open-apps-mode', {});
setQortalUrl("qortal://"); setQortalUrl('qortal://');
} }
} catch (error) {} } catch (error) {
console.log(error);
}
}; };
return ( return (
<> <>
<AppsContainer <AppsContainer
sx={{ sx={{
justifyContent: "flex-start", justifyContent: 'flex-start',
}} }}
> >
<AppLibrarySubTitle <AppLibrarySubTitle
sx={{ sx={{
fontSize: "30px", fontSize: '30px',
}} }}
> >
Apps Dashboard Apps Dashboard
@ -57,19 +61,22 @@ export const AppsHomeDesktop = ({
<AppsContainer <AppsContainer
sx={{ sx={{
justifyContent: "flex-start", justifyContent: 'flex-start',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
gap: "20px", gap: '20px',
alignItems: "center", alignItems: 'center',
backgroundColor: "#1f2023", backgroundColor:
padding: "7px", theme.palette.mode === 'dark'
borderRadius: "20px", ? 'rgba(41, 41, 43, 1)'
width: "100%", : 'rgb(209, 209, 209)',
maxWidth: "500px", padding: '7px',
borderRadius: '20px',
width: '100%',
maxWidth: '500px',
}} }}
> >
<Input <Input
@ -83,24 +90,24 @@ export const AppsHomeDesktop = ({
autoCorrect="off" autoCorrect="off"
placeholder="qortal://" placeholder="qortal://"
sx={{ sx={{
width: "100%", width: '100%',
color: "white", color: theme.palette.mode === 'dark' ? 'white' : 'black',
"& .MuiInput-input::placeholder": { '& .MuiInput-input::placeholder': {
color: "rgba(84, 84, 84, 0.70) !important", color: 'rgba(84, 84, 84, 0.70) !important',
fontSize: "20px", fontSize: '20px',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: 400, fontWeight: 400,
lineHeight: "120%", // 24px lineHeight: '120%', // 24px
letterSpacing: "0.15px", letterSpacing: '0.15px',
opacity: 1, opacity: 1,
}, },
"&:focus": { '&:focus': {
outline: "none", outline: 'none',
}, },
// Add any additional styles for the input here // Add any additional styles for the input here
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && qortalUrl) { if (e.key === 'Enter' && qortalUrl) {
openQortalUrl(); openQortalUrl();
} }
}} }}
@ -108,7 +115,7 @@ export const AppsHomeDesktop = ({
<ButtonBase onClick={() => openQortalUrl()}> <ButtonBase onClick={() => openQortalUrl()}>
<ArrowOutwardIcon <ArrowOutwardIcon
sx={{ sx={{
color: qortalUrl ? "white" : "rgba(84, 84, 84, 0.70)", color: qortalUrl ? 'white' : 'rgba(84, 84, 84, 0.70)',
}} }}
/> />
</ButtonBase> </ButtonBase>
@ -119,18 +126,18 @@ export const AppsHomeDesktop = ({
<AppsContainer <AppsContainer
sx={{ sx={{
gap: "50px", gap: '50px',
justifyContent: "flex-start", justifyContent: 'flex-start',
}} }}
> >
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setMode("library"); setMode('library');
}} }}
> >
<AppCircleContainer <AppCircleContainer
sx={{ sx={{
gap: !isMobile ? "10px" : "5px", gap: !isMobile ? '10px' : '5px',
}} }}
> >
<AppCircle> <AppCircle>
@ -150,7 +157,7 @@ export const AppsHomeDesktop = ({
/> />
</AppsContainer> </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 { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -16,84 +23,97 @@ import {
PublishQAppCTAParent, PublishQAppCTAParent,
PublishQAppCTARight, PublishQAppCTARight,
PublishQAppDotsBG, PublishQAppDotsBG,
} from "./Apps-styles"; } from './Apps-styles';
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material"; import { Avatar, Box, ButtonBase, InputBase, styled } from '@mui/material';
import { Add } from "@mui/icons-material"; import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from "../../App"; import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from "../../assets/svgs/LogoSelected.svg"; import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import IconSearch from "../../assets/svgs/Search.svg"; import IconSearch from '../../assets/svgs/Search.svg';
import IconClearInput from "../../assets/svgs/ClearInput.svg"; import IconClearInput from '../../assets/svgs/ClearInput.svg';
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg"; import qappDevelopText from '../../assets/svgs/qappDevelopText.svg';
import qappDots from "../../assets/svgs/qappDots.svg"; import qappDots from '../../assets/svgs/qappDots.svg';
import ReturnSVG from '../../assets/svgs/Return.svg' import ReturnSVG from '../../assets/svgs/Return.svg';
import { Spacer } from "../../common/Spacer"; import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from "./AppInfoSnippet"; import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from "react-virtuoso"; import { Virtuoso } from 'react-virtuoso';
import { executeEvent } from "../../utils/events"; import { executeEvent } from '../../utils/events';
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles"; import {
ComposeP,
MailIconImg,
ShowMessageReturnButton,
} from '../Group/Forum/Mail-styles';
const officialAppList = [ const officialAppList = [
"q-tube", 'q-tube',
"q-blog", 'q-blog',
"q-share", 'q-share',
"q-support", 'q-support',
"q-mail", 'q-mail',
"q-fund", 'q-fund',
"q-shop", 'q-shop',
"q-trade", 'q-trade',
"q-support", 'q-support',
"q-manager", 'q-manager',
"q-wallets", 'q-wallets',
"q-search" 'q-search',
]; ];
const ScrollerStyled = styled('div')({ // TODO: apply dark/light style
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-msOverflowStyle": "none",
});
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",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-msOverflowStyle": "none",
});
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => { const ScrollerStyled = styled('div')({
const [searchValue, setSearchValue] = useState(""); // Hide scrollbar for WebKit browsers (Chrome, Safari)
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
'-msOverflowStyle': 'none',
});
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',
},
// Hide scrollbar for Firefox
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
'-msOverflowStyle': 'none',
});
export const AppsLibrary = ({
availableQapps,
setMode,
myName,
hasPublishApp,
isShow,
categories = { categories },
}) => {
const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef(); const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext); const { rootHeight } = useContext(MyContext);
const officialApps = useMemo(() => { const officialApps = useMemo(() => {
return availableQapps.filter( return availableQapps.filter(
(app) => (app) =>
app.service === "APP" && app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase()) officialAppList.includes(app?.name?.toLowerCase())
); );
}, [availableQapps]); }, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic // Debounce logic
useEffect(() => { useEffect(() => {
@ -117,23 +137,28 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
}, [debouncedValue]); }, [debouncedValue]);
const rowRenderer = (index) => { const rowRenderer = (index) => {
let app = searchedList[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 ( return (
<AppsLibraryContainer sx={{ <AppsLibraryContainer
display: !isShow && 'none' sx={{
}}> display: !isShow && 'none',
<AppsWidthLimiter> }}
>
<AppsWidthLimiter>
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
width: "100%", width: '100%',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<AppsSearchContainer> <AppsSearchContainer>
@ -145,8 +170,8 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
sx={{ ml: 1, flex: 1 }} sx={{ ml: 1, flex: 1 }}
placeholder="Search for apps" placeholder="Search for apps"
inputProps={{ inputProps={{
"aria-label": "Search for apps", 'aria-label': 'Search for apps',
fontSize: "16px", fontSize: '16px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
@ -155,7 +180,7 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
{searchValue && ( {searchValue && (
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setSearchValue(""); setSearchValue('');
}} }}
> >
<img src={IconClearInput} /> <img src={IconClearInput} />
@ -164,23 +189,27 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
</AppsSearchRight> </AppsSearchRight>
</AppsSearchContainer> </AppsSearchContainer>
</Box> </Box>
</AppsWidthLimiter> </AppsWidthLimiter>
<Spacer height="25px" /> <Spacer height="25px" />
<ShowMessageReturnButton sx={{ <ShowMessageReturnButton
padding: '2px' sx={{
}} onClick={() => { padding: '2px',
executeEvent("navigateBack", {}); }}
onClick={() => {
}}> executeEvent('navigateBack', {});
<MailIconImg src={ReturnSVG} /> }}
<ComposeP>Return to Apps Dashboard</ComposeP> >
</ShowMessageReturnButton> <MailIconImg src={ReturnSVG} />
<Spacer height="25px" /> <ComposeP>Return to Apps Dashboard</ComposeP>
{searchedList?.length > 0 ? ( </ShowMessageReturnButton>
<AppsWidthLimiter> <Spacer height="25px" />
<StyledVirtuosoContainer sx={{ {searchedList?.length > 0 ? (
height: rootHeight <AppsWidthLimiter>
}}> <StyledVirtuosoContainer
sx={{
height: rootHeight,
}}
>
<Virtuoso <Virtuoso
ref={virtuosoRef} ref={virtuosoRef}
data={searchedList} data={searchedList}
@ -188,14 +217,14 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
atBottomThreshold={50} atBottomThreshold={50}
followOutput="smooth" followOutput="smooth"
components={{ components={{
Scroller: ScrollerStyled // Use the styled scroller component Scroller: ScrollerStyled, // Use the styled scroller component
}} }}
/> />
</StyledVirtuosoContainer> </StyledVirtuosoContainer>
</AppsWidthLimiter> </AppsWidthLimiter>
) : ( ) : (
<> <>
<AppsWidthLimiter> <AppsWidthLimiter>
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle> <AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
<AppsContainer> <AppsContainer>
@ -203,14 +232,14 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
return ( return (
<ButtonBase <ButtonBase
sx={{ sx={{
height: "80px", height: '80px',
width: "60px", width: '60px',
}} }}
onClick={()=> { onClick={() => {
// executeEvent("addTab", { // executeEvent("addTab", {
// data: qapp // data: qapp
// }) // })
executeEvent("selectedAppInfo", { executeEvent('selectedAppInfo', {
data: qapp, data: qapp,
}); });
}} }}
@ -218,13 +247,13 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
<AppCircleContainer> <AppCircleContainer>
<AppCircle <AppCircle
sx={{ sx={{
border: "none", border: 'none',
}} }}
> >
<Avatar <Avatar
sx={{ sx={{
height: "31px", height: '31px',
width: "31px", width: '31px',
}} }}
alt={qapp?.name} alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -233,8 +262,8 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
> >
<img <img
style={{ style={{
width: "31px", width: '31px',
height: "auto", height: 'auto',
}} }}
src={LogoSelected} src={LogoSelected}
alt="center-icon" alt="center-icon"
@ -250,77 +279,87 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
})} })}
</AppsContainer> </AppsContainer>
<Spacer height="30px" /> <Spacer height="30px" />
<AppLibrarySubTitle>{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}</AppLibrarySubTitle> <AppLibrarySubTitle>
{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
</AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
</AppsWidthLimiter> </AppsWidthLimiter>
<PublishQAppCTAParent> <PublishQAppCTAParent>
<PublishQAppCTALeft> <PublishQAppCTALeft>
<PublishQAppDotsBG> <PublishQAppDotsBG>
<img src={qappDots} />
<img src={qappDots} />
</PublishQAppDotsBG> </PublishQAppDotsBG>
<Spacer width="29px" /> <Spacer width="29px" />
<img src={qappDevelopText} /> <img src={qappDevelopText} />
</PublishQAppCTALeft> </PublishQAppCTALeft>
<PublishQAppCTARight onClick={()=> { <PublishQAppCTARight
setMode('publish') onClick={() => {
}}> setMode('publish');
<PublishQAppCTAButton> }}
{hasPublishApp ? 'Update' : 'Publish'} >
</PublishQAppCTAButton> <PublishQAppCTAButton>
<Spacer width="20px" /> {hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTARight> </PublishQAppCTAButton>
</PublishQAppCTAParent> <Spacer width="20px" />
<AppsWidthLimiter> </PublishQAppCTARight>
</PublishQAppCTAParent>
<AppsWidthLimiter>
<Spacer height="18px" /> <Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle> <AppLibrarySubTitle>Categories</AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
<AppsWidthLimiter sx={{ <AppsWidthLimiter
flexDirection: 'row', sx={{
overflowX: 'auto', flexDirection: 'row',
width: '100%', overflowX: 'auto',
gap: '5px', width: '100%',
"::-webkit-scrollbar": { gap: '5px',
width: "0px", '::-webkit-scrollbar': {
height: "0px", width: '0px',
}, height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: "none", // Hide scrollbar for Firefox
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
"-msOverflowStyle": "none", // Hide scrollbar for IE and older Edge
}}> '-msOverflowStyle': 'none',
{categories?.map((category)=> { }}
return ( >
<ButtonBase key={category?.id} onClick={()=> { {categories?.map((category) => {
executeEvent('selectedCategory', { return (
data: category <ButtonBase
}) key={category?.id}
}}> onClick={() => {
<Box sx={{ executeEvent('selectedCategory', {
display: 'flex', data: category,
alignItems: 'center', });
justifyContent: 'center', }}
height: '110px', >
width: '110px', <Box
background: 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)', sx={{
color: '#1D1D1E', display: 'flex',
fontWeight: 700, alignItems: 'center',
fontSize: '16px', justifyContent: 'center',
flexShrink: 0, height: '110px',
borderRadius: '11px' width: '110px',
}}> background:
{category?.name} 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
</Box> color: '#1D1D1E',
</ButtonBase> fontWeight: 700,
) fontSize: '16px',
})} flexShrink: 0,
borderRadius: '11px',
}}
>
{category?.name}
</Box>
</ButtonBase>
);
})}
</AppsWidthLimiter> </AppsWidthLimiter>
</AppsWidthLimiter> </AppsWidthLimiter>
</> </>
)} )}
</AppsLibraryContainer> </AppsLibraryContainer>
); );
}; };

View File

@ -5,7 +5,7 @@ import React, {
useMemo, useMemo,
useRef, useRef,
useState, useState,
} from "react"; } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -23,77 +23,87 @@ import {
PublishQAppCTAParent, PublishQAppCTAParent,
PublishQAppCTARight, PublishQAppCTARight,
PublishQAppDotsBG, PublishQAppDotsBG,
} from "./Apps-styles"; } from './Apps-styles';
import { Avatar, Box, ButtonBase, InputBase, Typography, styled } from "@mui/material"; import {
import { Add } from "@mui/icons-material"; Avatar,
import { MyContext, getBaseApiReact } from "../../App"; Box,
import LogoSelected from "../../assets/svgs/LogoSelected.svg"; ButtonBase,
import IconSearch from "../../assets/svgs/Search.svg"; InputBase,
import IconClearInput from "../../assets/svgs/ClearInput.svg"; Typography,
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg"; styled,
import qappLibraryText from "../../assets/svgs/qappLibraryText.svg"; } from '@mui/material';
import RefreshIcon from "@mui/icons-material/Refresh"; 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 { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from "./AppInfoSnippet"; import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from "react-virtuoso"; import { Virtuoso } from 'react-virtuoso';
import { executeEvent } from "../../utils/events"; import { executeEvent } from '../../utils/events';
import { import {
AppsDesktopLibraryBody, AppsDesktopLibraryBody,
AppsDesktopLibraryHeader, AppsDesktopLibraryHeader,
} from "./AppsDesktop-styles"; } from './AppsDesktop-styles';
import { AppsNavBarDesktop } from "./AppsNavBarDesktop"; import ReturnSVG from '../../assets/svgs/Return.svg';
import ReturnSVG from '../../assets/svgs/Return.svg' import {
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles"; ComposeP,
MailIconImg,
ShowMessageReturnButton,
} from '../Group/Forum/Mail-styles';
const officialAppList = [ const officialAppList = [
"q-tube", 'q-tube',
"q-blog", 'q-blog',
"q-share", 'q-share',
"q-support", 'q-support',
"q-mail", 'q-mail',
"q-fund", 'q-fund',
"q-shop", 'q-shop',
"q-trade", 'q-trade',
"q-support", 'q-support',
"q-manager", 'q-manager',
"q-mintership", 'q-mintership',
"q-wallets", 'q-wallets',
"q-search" 'q-search',
]; ];
const ScrollerStyled = styled("div")({ const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari) // Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": { '::-webkit-scrollbar': {
width: "0px", width: '0px',
height: "0px", height: '0px',
}, },
// Hide scrollbar for Firefox // Hide scrollbar for Firefox
scrollbarWidth: "none", scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge // Hide scrollbar for IE and older Edge
"-msOverflowStyle": "none", '-msOverflowStyle': 'none',
}); });
const StyledVirtuosoContainer = styled("div")({ const StyledVirtuosoContainer = styled('div')({
position: "relative", position: 'relative',
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari) // Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": { '::-webkit-scrollbar': {
width: "0px", width: '0px',
height: "0px", height: '0px',
}, },
// Hide scrollbar for Firefox // Hide scrollbar for Firefox
scrollbarWidth: "none", scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge // Hide scrollbar for IE and older Edge
"-msOverflowStyle": "none", '-msOverflowStyle': 'none',
}); });
export const AppsLibraryDesktop = ({ export const AppsLibraryDesktop = ({
@ -103,20 +113,20 @@ export const AppsLibraryDesktop = ({
hasPublishApp, hasPublishApp,
isShow, isShow,
categories, categories,
getQapps getQapps,
}) => { }) => {
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef(); const virtuosoRef = useRef();
const officialApps = useMemo(() => { const officialApps = useMemo(() => {
return availableQapps.filter( return availableQapps.filter(
(app) => (app) =>
app.service === "APP" && app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase()) officialAppList.includes(app?.name?.toLowerCase())
); );
}, [availableQapps]); }, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic // Debounce logic
useEffect(() => { useEffect(() => {
@ -125,7 +135,7 @@ export const AppsLibraryDesktop = ({
}, 350); }, 350);
setTimeout(() => { setTimeout(() => {
virtuosoRef.current.scrollToIndex({ virtuosoRef.current.scrollToIndex({
index: 0 index: 0,
}); });
}, 500); }, 500);
// Cleanup timeout if searchValue changes before the timeout completes // Cleanup timeout if searchValue changes before the timeout completes
@ -138,8 +148,13 @@ export const AppsLibraryDesktop = ({
const searchedList = useMemo(() => { const searchedList = useMemo(() => {
if (!debouncedValue) return []; if (!debouncedValue) return [];
return availableQapps.filter((app) => return availableQapps.filter(
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase())) (app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) ||
(app?.metadata?.title &&
app?.metadata?.title
?.toLowerCase()
.includes(debouncedValue.toLowerCase()))
); );
}, [debouncedValue]); }, [debouncedValue]);
@ -151,7 +166,7 @@ export const AppsLibraryDesktop = ({
app={app} app={app}
myName={myName} myName={myName}
parentStyles={{ parentStyles={{
padding: '0px 10px' padding: '0px 10px',
}} }}
/> />
); );
@ -160,111 +175,112 @@ export const AppsLibraryDesktop = ({
return ( return (
<AppsLibraryContainer <AppsLibraryContainer
sx={{ sx={{
display: !isShow && "none", display: !isShow && 'none',
padding: "0px", padding: '0px',
height: "100vh", height: '100vh',
overflow: "hidden", overflow: 'hidden',
paddingTop: '30px' paddingTop: '30px',
}} }}
> >
<AppsDesktopLibraryHeader <AppsDesktopLibraryHeader
sx={{ sx={{
maxWidth: "1500px", maxWidth: '1500px',
width: "90%", width: '90%',
}} }}
> >
<AppsWidthLimiter> <AppsWidthLimiter>
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
width: "100%", width: '100%',
justifyContent: "space-between", justifyContent: 'space-between',
}} }}
> >
<img src={qappLibraryText} /> <img src={qappLibraryText} />
<Box sx={{ <Box
display: 'flex',
gap: '20px',
alignItems: 'center'
}}>
<AppsSearchContainer
sx={{ sx={{
width: "412px", display: 'flex',
gap: '20px',
alignItems: 'center',
}} }}
> >
<AppsSearchLeft> <AppsSearchContainer
<img src={IconSearch} /> sx={{
<InputBase width: '412px',
value={searchValue} }}
onChange={(e) => setSearchValue(e.target.value)} >
sx={{ ml: 1, flex: 1 }} <AppsSearchLeft>
placeholder="Search for apps" <img src={IconSearch} />
inputProps={{ <InputBase
"aria-label": "Search for apps", value={searchValue}
fontSize: "16px", onChange={(e) => setSearchValue(e.target.value)}
fontWeight: 400, sx={{ ml: 1, flex: 1 }}
placeholder="Search for apps"
inputProps={{
'aria-label': 'Search for apps',
fontSize: '16px',
fontWeight: 400,
}}
/>
</AppsSearchLeft>
<AppsSearchRight>
{searchValue && (
<ButtonBase
onClick={() => {
setSearchValue('');
}}
>
<img src={IconClearInput} />
</ButtonBase>
)}
</AppsSearchRight>
</AppsSearchContainer>
<ButtonBase
onClick={(e) => {
getQapps();
}}
>
<RefreshIcon
sx={{
color: 'rgba(250, 250, 250, 0.5)',
width: '40px',
height: 'auto',
}} }}
/> />
</AppsSearchLeft> </ButtonBase>
<AppsSearchRight>
{searchValue && (
<ButtonBase
onClick={() => {
setSearchValue("");
}}
>
<img src={IconClearInput} />
</ButtonBase>
)}
</AppsSearchRight>
</AppsSearchContainer>
<ButtonBase
onClick={(e) => {
getQapps()
}}
>
<RefreshIcon
sx={{
color: "rgba(250, 250, 250, 0.5)",
width: '40px',
height: 'auto'
}}
/>
</ButtonBase>
</Box> </Box>
</Box> </Box>
</AppsWidthLimiter> </AppsWidthLimiter>
</AppsDesktopLibraryHeader> </AppsDesktopLibraryHeader>
<AppsDesktopLibraryBody <AppsDesktopLibraryBody
sx={{ sx={{
height: `calc(100vh - 36px)`, height: `calc(100vh - 36px)`,
overflow: "auto", overflow: 'auto',
padding: "0px", padding: '0px',
alignItems: "center", alignItems: 'center',
}} }}
> >
<AppsDesktopLibraryBody <AppsDesktopLibraryBody
sx={{ sx={{
height: `calc(100vh - 36px)`, height: `calc(100vh - 36px)`,
flexGrow: "unset", flexGrow: 'unset',
maxWidth: "1500px", maxWidth: '1500px',
width: "90%", width: '90%',
}} }}
> >
<Spacer height="70px" /> <Spacer height="70px" />
<ShowMessageReturnButton sx={{ <ShowMessageReturnButton
padding: '2px' sx={{
}} onClick={() => { padding: '2px',
executeEvent("navigateBack", {}); }}
}}> onClick={() => {
<MailIconImg src={ReturnSVG} /> executeEvent('navigateBack', {});
<ComposeP>Return to Apps Dashboard</ComposeP> }}
</ShowMessageReturnButton> >
<Spacer height="20px" /> <MailIconImg src={ReturnSVG} />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
<Spacer height="20px" />
{searchedList?.length > 0 ? ( {searchedList?.length > 0 ? (
<AppsWidthLimiter> <AppsWidthLimiter>
<StyledVirtuosoContainer <StyledVirtuosoContainer
@ -292,46 +308,48 @@ export const AppsLibraryDesktop = ({
<> <>
<AppLibrarySubTitle <AppLibrarySubTitle
sx={{ sx={{
fontSize: "30px", fontSize: '30px',
}} }}
> >
Official Apps Official Apps
</AppLibrarySubTitle> </AppLibrarySubTitle>
<Spacer height="45px" /> <Spacer height="45px" />
<AppsContainer sx={{ <AppsContainer
gap: '50px', sx={{
justifyContent: 'flex-start' gap: '50px',
}}> justifyContent: 'flex-start',
}}
>
{officialApps?.map((qapp) => { {officialApps?.map((qapp) => {
return ( return (
<ButtonBase <ButtonBase
sx={{ sx={{
// height: "80px", // height: "80px",
width: "80px", width: '80px',
}} }}
onClick={() => { onClick={() => {
// executeEvent("addTab", { // executeEvent("addTab", {
// data: qapp // data: qapp
// }) // })
executeEvent("selectedAppInfo", { executeEvent('selectedAppInfo', {
data: qapp, data: qapp,
}); });
}} }}
> >
<AppCircleContainer <AppCircleContainer
sx={{ sx={{
gap: "10px", gap: '10px',
}} }}
> >
<AppCircle <AppCircle
sx={{ sx={{
border: "none", border: 'none',
}} }}
> >
<Avatar <Avatar
sx={{ sx={{
height: "42px", height: '42px',
width: "42px", width: '42px',
}} }}
alt={qapp?.name} alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -340,8 +358,8 @@ export const AppsLibraryDesktop = ({
> >
<img <img
style={{ style={{
width: "31px", width: '31px',
height: "auto", height: 'auto',
}} }}
src={LogoSelected} src={LogoSelected}
alt="center-icon" alt="center-icon"
@ -359,30 +377,30 @@ export const AppsLibraryDesktop = ({
<Spacer height="80px" /> <Spacer height="80px" />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
gap: "250px", gap: '250px',
display: "flex", display: 'flex',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<AppLibrarySubTitle <AppLibrarySubTitle
sx={{ sx={{
fontSize: "30px", fontSize: '30px',
width: "100%", width: '100%',
textAlign: "start", textAlign: 'start',
}} }}
> >
{hasPublishApp ? "Update Apps!" : "Create Apps!"} {hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
</AppLibrarySubTitle> </AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
<PublishQAppCTAParent <PublishQAppCTAParent
sx={{ sx={{
gap: "25px", gap: '25px',
}} }}
> >
<PublishQAppCTALeft> <PublishQAppCTALeft>
@ -394,11 +412,11 @@ export const AppsLibraryDesktop = ({
</PublishQAppCTALeft> </PublishQAppCTALeft>
<PublishQAppCTARight <PublishQAppCTARight
onClick={() => { onClick={() => {
setMode("publish"); setMode('publish');
}} }}
> >
<PublishQAppCTAButton> <PublishQAppCTAButton>
{hasPublishApp ? "Update" : "Publish"} {hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton> </PublishQAppCTAButton>
<Spacer width="20px" /> <Spacer width="20px" />
</PublishQAppCTARight> </PublishQAppCTARight>
@ -406,13 +424,13 @@ export const AppsLibraryDesktop = ({
</Box> </Box>
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<AppLibrarySubTitle <AppLibrarySubTitle
sx={{ sx={{
fontSize: "30px", fontSize: '30px',
}} }}
> >
Categories Categories
@ -420,58 +438,57 @@ export const AppsLibraryDesktop = ({
<Spacer height="18px" /> <Spacer height="18px" />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
gap: "20px", gap: '20px',
flexWrap: "wrap", flexWrap: 'wrap',
}} }}
> >
<ButtonBase <ButtonBase
onClick={() => {
onClick={() => { executeEvent('selectedCategory', {
executeEvent("selectedCategory", { data: {
data: { id: 'all',
id: 'all', name: 'All',
name: 'All' },
}, });
}); }}
}} >
> <Box
<Box sx={{
sx={{ display: 'flex',
display: "flex", alignItems: 'center',
alignItems: "center", justifyContent: 'center',
justifyContent: "center", height: '60px',
height: "60px", padding: '0px 24px',
padding: "0px 24px", border: '4px solid #10242F',
border: "4px solid #10242F", borderRadius: '6px',
borderRadius: "6px", boxShadow: '2px 4px 0px 0px #000000',
boxShadow: "2px 4px 0px 0px #000000", }}
}} >
> All
All </Box>
</Box> </ButtonBase>
</ButtonBase>
{categories?.map((category) => { {categories?.map((category) => {
return ( return (
<ButtonBase <ButtonBase
key={category?.id} key={category?.id}
onClick={() => { onClick={() => {
executeEvent("selectedCategory", { executeEvent('selectedCategory', {
data: category, data: category,
}); });
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
height: "60px", height: '60px',
padding: "0px 24px", padding: '0px 24px',
border: "4px solid #10242F", border: '4px solid #10242F',
borderRadius: "6px", borderRadius: '6px',
boxShadow: "2px 4px 0px 0px #000000", boxShadow: '2px 4px 0px 0px #000000',
}} }}
> >
{category?.name} {category?.name}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useMemo, useState } from "react"; import React, { useContext, useEffect, useMemo, useState } from 'react';
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { import {
Box, Box,
Button, Button,
@ -9,12 +9,12 @@ import {
DialogActions, DialogActions,
DialogContent, DialogContent,
Typography, Typography,
} from "@mui/material"; } from '@mui/material';
import { CustomButton, CustomButtonAccept } from "../../App-styles"; import { CustomButton, CustomButtonAccept } from '../../styles/App-styles';
import { getBaseApiReact, MyContext } from "../../App"; import { getBaseApiReact, MyContext } from '../../App';
import { getFee } from "../../background"; import { getFee } from '../../background';
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { FidgetSpinner } from "react-loader-spinner"; import { FidgetSpinner } from 'react-loader-spinner';
export const JoinGroup = ({ memberGroups }) => { export const JoinGroup = ({ memberGroups }) => {
const { show, setTxList } = useContext(MyContext); const { show, setTxList } = useContext(MyContext);
@ -42,43 +42,45 @@ export const JoinGroup = ({ memberGroups }) => {
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("globalActionJoinGroup", handleJoinGroup); subscribeToEvent('globalActionJoinGroup', handleJoinGroup);
return () => { return () => {
unsubscribeFromEvent("globalActionJoinGroup", handleJoinGroup); unsubscribeFromEvent('globalActionJoinGroup', handleJoinGroup);
}; };
}, []); }, []);
const isInGroup = useMemo(()=> { const isInGroup = useMemo(() => {
return !!memberGroups.find((item)=> +item?.groupId === +groupInfo?.groupId) return !!memberGroups.find(
}, [memberGroups, groupInfo]) (item) => +item?.groupId === +groupInfo?.groupId
);
}, [memberGroups, groupInfo]);
const joinGroup = async (group, isOpen) => { const joinGroup = async (group, isOpen) => {
try { try {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee("JOIN_GROUP"); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: "Would you like to perform an JOIN_GROUP transaction?", message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoadingJoinGroup(true); setIsLoadingJoinGroup(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage("joinGroup", { .sendMessage('joinGroup', {
groupId, groupId,
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: 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) { if (isOpen) {
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: "joined-group", type: 'joined-group',
label: `Joined Group ${group?.groupName}: awaiting confirmation`, label: `Joined Group ${group?.groupName}: awaiting confirmation`,
labelDone: `Joined Group ${group?.groupName}: success!`, labelDone: `Joined Group ${group?.groupName}: success!`,
done: false, done: false,
@ -90,7 +92,7 @@ export const JoinGroup = ({ memberGroups }) => {
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: "joined-group-request", type: 'joined-group-request',
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`, label: `Requested to join Group ${group?.groupName}: awaiting confirmation`,
labelDone: `Requested to join Group ${group?.groupName}: success!`, labelDone: `Requested to join Group ${group?.groupName}: success!`,
done: false, done: false,
@ -105,7 +107,7 @@ export const JoinGroup = ({ memberGroups }) => {
return; return;
} else { } else {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: response?.error, message: response?.error,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -114,8 +116,8 @@ export const JoinGroup = ({ memberGroups }) => {
}) })
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error.message || "An error occurred", message: error.message || 'An error occurred',
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -138,37 +140,37 @@ export const JoinGroup = ({ memberGroups }) => {
{!groupInfo && ( {!groupInfo && (
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "150px", height: '150px',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
{" "} {' '}
<CircularProgress <CircularProgress
size={25} size={25}
sx={{ sx={{
color: "white", color: 'white',
}} }}
/>{" "} />{' '}
</Box> </Box>
)} )}
<Box <Box
sx={{ sx={{
width: "325px", width: '325px',
height: "auto", height: 'auto',
maxHeight: "400px", maxHeight: '400px',
display: !groupInfo ? "none" : "flex", display: !groupInfo ? 'none' : 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "10px", gap: '10px',
padding: "10px", padding: '10px',
}} }}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "15px", fontSize: '15px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
@ -176,7 +178,7 @@ export const JoinGroup = ({ memberGroups }) => {
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
fontSize: "15px", fontSize: '15px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
@ -185,7 +187,7 @@ export const JoinGroup = ({ memberGroups }) => {
{groupInfo?.description && ( {groupInfo?.description && (
<Typography <Typography
sx={{ sx={{
fontSize: "15px", fontSize: '15px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
@ -193,19 +195,19 @@ export const JoinGroup = ({ memberGroups }) => {
</Typography> </Typography>
)} )}
{isInGroup && ( {isInGroup && (
<Typography <Typography
sx={{ sx={{
fontSize: "14px", fontSize: '14px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
*You are already in this group! *You are already in this group!
</Typography> </Typography>
)} )}
{!isInGroup && groupInfo?.isOpen === false && ( {!isInGroup && groupInfo?.isOpen === false && (
<Typography <Typography
sx={{ sx={{
fontSize: "14px", fontSize: '14px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
@ -216,32 +218,34 @@ export const JoinGroup = ({ memberGroups }) => {
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<ButtonBase onClick={() => { <ButtonBase
onClick={() => {
joinGroup(groupInfo, groupInfo?.isOpen); joinGroup(groupInfo, groupInfo?.isOpen);
setIsOpen(false); setIsOpen(false);
}} disabled={isInGroup}>
<CustomButtonAccept
color="black"
bgColor="var(--green)"
sx={{
minWidth: "102px",
height: "45px",
fontSize: '16px',
opacity: isInGroup ? 0.1 : 1
}} }}
disabled={isInGroup}
> >
Join <CustomButtonAccept
</CustomButtonAccept> color="black"
bgColor="var(--green)"
sx={{
minWidth: '102px',
height: '45px',
fontSize: '16px',
opacity: isInGroup ? 0.1 : 1,
}}
>
Join
</CustomButtonAccept>
</ButtonBase> </ButtonBase>
<CustomButtonAccept <CustomButtonAccept
color="black" color="black"
bgColor="var(--danger)" bgColor="var(--danger)"
sx={{ sx={{
minWidth: "102px", minWidth: '102px',
height: "45px", height: '45px',
}} }}
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
> >
@ -259,14 +263,14 @@ export const JoinGroup = ({ memberGroups }) => {
{isLoadingJoinGroup && ( {isLoadingJoinGroup && (
<Box <Box
sx={{ sx={{
position: "absolute", position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
}} }}
> >
<FidgetSpinner <FidgetSpinner

View File

@ -10,15 +10,23 @@ import {
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { MyContext } from "../../App"; import { getBaseApiReact, MyContext } from "../../App";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events"; import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { validateAddress } from "../../utils/validateAddress";
export const BlockedUsersModal = ({ close }) => { 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 [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [addressesWithNames, setAddressesWithNames] = useState({})
const { getAllBlockedUsers, removeBlockFromList, addToBlockList } = useContext(MyContext); const { isShow, onCancel, onOk, show, message } = useModal();
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
useContext(MyContext);
const [blockedUsers, setBlockedUsers] = useState({ const [blockedUsers, setBlockedUsers] = useState({
addresses: {}, addresses: {},
names: {}, names: {},
@ -28,60 +36,162 @@ export const BlockedUsersModal = ({ close }) => {
}; };
useEffect(() => { useEffect(() => {
if(!isOpenBlockedModal) return
fetchBlockedUsers(); 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 ( return (
<Dialog <Dialog
open={true} open={isOpenBlockedModal}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle>Blocked Users</DialogTitle> <DialogTitle>Blocked Users</DialogTitle>
<DialogContent sx={{ <DialogContent
padding: '20px' sx={{
}}> padding: "20px",
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
}} }}
> >
<TextField <Box
placeholder="Name" sx={{
value={value} display: "flex",
onChange={(e) => { alignItems: "center",
setValue(e.target.value); gap: "10px",
}} }}
/> >
<Button variant="contained" onClick={async ()=> { <TextField
try { placeholder="Name or address"
if(!value) return value={value}
await addToBlockList(undefined, value) onChange={(e) => {
fetchBlockedUsers() setValue(e.target.value);
setHasChanged(true) }}
} catch (error) { />
console.error(error) <Button
} sx={{
}}>Block</Button> flexShrink: 0,
</Box> }}
variant="contained"
onClick={blockUser}
>
Block
</Button>
</Box>
{Object.entries(blockedUsers?.addresses).length > 0 && ( {Object.entries(blockedUsers?.addresses).length > 0 && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked Users for Chat ( addresses ) Blocked addresses- blocks processing of txs
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
<Spacer height="10px" />
</> </>
)} )}
<Box sx={{ <Box
display: 'flex', sx={{
flexDirection: 'column', display: "flex",
gap: '10px' flexDirection: "column",
}}> gap: "10px",
}}
>
{Object.entries(blockedUsers?.addresses || {})?.map( {Object.entries(blockedUsers?.addresses || {})?.map(
([key, value]) => { ([key, value]) => {
return ( return (
@ -90,18 +200,22 @@ export const BlockedUsersModal = ({ close }) => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "10px",
width: '100%', width: "100%",
justifyContent: 'space-between' justifyContent: "space-between",
}} }}
> >
<Typography>{key}</Typography> <Typography>{addressesWithNames[key] || key}</Typography>
<Button <Button
sx={{
flexShrink: 0,
}}
size="small"
variant="contained" variant="contained"
onClick={async () => { onClick={async () => {
try { try {
await removeBlockFromList(key, undefined); await removeBlockFromList(key, undefined);
setHasChanged(true); setHasChanged(true);
setValue('') setValue("");
fetchBlockedUsers(); fetchBlockedUsers();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -119,17 +233,19 @@ export const BlockedUsersModal = ({ close }) => {
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked Users for QDN and Chat (names) Blocked names for QDN
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
<Box sx={{ <Box
display: 'flex', sx={{
flexDirection: 'column', display: "flex",
gap: '10px' flexDirection: "column",
}}> gap: "10px",
}}
>
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => { {Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
return ( return (
<Box <Box
@ -137,12 +253,16 @@ export const BlockedUsersModal = ({ close }) => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "10px",
width: '100%', width: "100%",
justifyContent: 'space-between' justifyContent: "space-between",
}} }}
> >
<Typography>{key}</Typography> <Typography>{key}</Typography>
<Button <Button
size="small"
sx={{
flexShrink: 0,
}}
variant="contained" variant="contained"
onClick={async () => { onClick={async () => {
try { try {
@ -175,16 +295,67 @@ export const BlockedUsersModal = ({ close }) => {
}, },
}} }}
variant="contained" variant="contained"
onClick={()=> { onClick={() => {
if(hasChanged){ if (hasChanged) {
executeEvent('updateChatMessagesWithBlocks', true) executeEvent("updateChatMessagesWithBlocks", true);
} }
close() setIsOpenBlockedModal(false);
}} }}
> >
close close
</Button> </Button>
</DialogActions> </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> </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, useMemo,
useRef, useRef,
useState, useState,
} from "react"; } from 'react';
import { import {
Avatar, Avatar,
Box, Box,
@ -14,8 +14,8 @@ import {
IconButton, IconButton,
Skeleton, Skeleton,
Typography, Typography,
} from "@mui/material"; } from '@mui/material';
import { ShowMessage } from "./ShowMessageWithoutModal"; import { ShowMessage } from './ShowMessageWithoutModal';
import { import {
ComposeP, ComposeP,
GroupContainer, GroupContainer,
@ -28,34 +28,33 @@ import {
ThreadInfoColumn, ThreadInfoColumn,
ThreadInfoColumnNameP, ThreadInfoColumnNameP,
ThreadInfoColumnTime, ThreadInfoColumnTime,
} from "./Mail-styles"; } from './Mail-styles';
import { Spacer } from "../../../common/Spacer"; import { Spacer } from '../../../common/Spacer';
import { threadIdentifier } from "./GroupMail"; import { threadIdentifier } from './GroupMail';
import LazyLoad from "../../../common/LazyLoad"; import ReturnSVG from '../../../assets/svgs/Return.svg';
import ReturnSVG from "../../../assets/svgs/Return.svg"; import { NewThread } from './NewThread';
import { NewThread } from "./NewThread";
import { import {
decryptPublishes, decryptPublishes,
getTempPublish, getTempPublish,
handleUnencryptedPublishes, handleUnencryptedPublishes,
} from "../../Chat/GroupAnnouncements"; } from '../../Chat/GroupAnnouncements';
import { LoadingSnackbar } from "../../Snackbar/LoadingSnackbar"; import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import { subscribeToEvent, unsubscribeFromEvent } from "../../../utils/events"; import { subscribeToEvent, unsubscribeFromEvent } from '../../../utils/events';
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from '@mui/icons-material/Refresh';
import { import {
getArbitraryEndpointReact, getArbitraryEndpointReact,
getBaseApiReact, getBaseApiReact,
isMobile, isMobile,
} from "../../../App"; } from '../../../App';
import { import {
ArrowDownward as ArrowDownwardIcon, ArrowDownward as ArrowDownwardIcon,
ArrowUpward as ArrowUpwardIcon, ArrowUpward as ArrowUpwardIcon,
} from "@mui/icons-material"; } from '@mui/icons-material';
import { addDataPublishesFunc, getDataPublishesFunc } from "../Group"; import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
import { RequestQueueWithPromise } from "../../../utils/queue/queue"; import { RequestQueueWithPromise } from '../../../utils/queue/queue';
import { CustomLoader } from "../../../common/CustomLoader"; import { CustomLoader } from '../../../common/CustomLoader';
import { WrapperUserAction } from "../../WrapperUserAction"; import { WrapperUserAction } from '../../WrapperUserAction';
import { formatTimestampForum } from "../../../utils/time"; import { formatTimestampForum } from '../../../utils/time';
const requestQueueSaveToLocal = new RequestQueueWithPromise(1); const requestQueueSaveToLocal = new RequestQueueWithPromise(1);
const requestQueueDownloadPost = new RequestQueueWithPromise(3); const requestQueueDownloadPost = new RequestQueueWithPromise(3);
interface ThreadProps { interface ThreadProps {
@ -65,14 +64,10 @@ interface ThreadProps {
members: any; members: any;
} }
const getEncryptedResource = async ({ const getEncryptedResource = async (
name, { name, identifier, secretKey, resource, groupId, dataPublishes },
identifier, isPrivate
secretKey, ) => {
resource,
groupId,
dataPublishes,
}, isPrivate) => {
let data = dataPublishes[`${name}-${identifier}`]; let data = dataPublishes[`${name}-${identifier}`];
if ( if (
!data || !data ||
@ -92,15 +87,18 @@ const getEncryptedResource = async ({
}; };
} }
data = await res.text(); data = await res.text();
if (data?.error || typeof data !== "string") return; if (data?.error || typeof data !== 'string') return;
await requestQueueSaveToLocal.enqueue(() => { await requestQueueSaveToLocal.enqueue(() => {
return addDataPublishesFunc({ ...resource, data }, groupId, "thmsg"); return addDataPublishesFunc({ ...resource, data }, groupId, 'thmsg');
}); });
} else { } else {
data = data.data; data = data.data;
} }
const response = isPrivate === false ? handleUnencryptedPublishes([data]) : await decryptPublishes([{ data }], secretKey); const response =
isPrivate === false
? handleUnencryptedPublishes([data])
: await decryptPublishes([{ data }], secretKey);
const messageData = response[0]; const messageData = response[0];
return messageData.decryptedData; return messageData.decryptedData;
@ -115,7 +113,7 @@ export const Thread = ({
secretKey, secretKey,
getSecretKey, getSecretKey,
updateThreadActivityCurrentThread, updateThreadActivityCurrentThread,
isPrivate isPrivate,
}: ThreadProps) => { }: ThreadProps) => {
const [tempPublishedList, setTempPublishedList] = useState([]); const [tempPublishedList, setTempPublishedList] = useState([]);
const [messages, setMessages] = useState<any[]>([]); const [messages, setMessages] = useState<any[]>([]);
@ -129,7 +127,7 @@ export const Thread = ({
// Update: Use a new ref for the scrollable container // Update: Use a new ref for the scrollable container
const threadContainerRef = useRef(null); const threadContainerRef = useRef(null);
const threadBeginningRef = useRef(null) const threadBeginningRef = useRef(null);
// New state variables // New state variables
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
const [isAtBottom, setIsAtBottom] = useState(false); const [isAtBottom, setIsAtBottom] = useState(false);
@ -140,7 +138,7 @@ export const Thread = ({
const dataPublishes = useRef({}); const dataPublishes = useRef({});
const getSavedData = useCallback(async (groupId) => { const getSavedData = useCallback(async (groupId) => {
const res = await getDataPublishesFunc(groupId, "thmsg"); const res = await getDataPublishesFunc(groupId, 'thmsg');
dataPublishes.current = res || {}; dataPublishes.current = res || {};
}, []); }, []);
@ -159,14 +157,17 @@ export const Thread = ({
const getIndividualMsg = async (message: any) => { const getIndividualMsg = async (message: any) => {
try { try {
const responseDataMessage = await getEncryptedResource({ const responseDataMessage = await getEncryptedResource(
identifier: message.identifier, {
name: message.name, identifier: message.identifier,
secretKey, name: message.name,
resource: message, secretKey,
groupId: groupInfo?.groupId, resource: message,
dataPublishes: dataPublishes.current, groupId: groupInfo?.groupId,
}, isPrivate); dataPublishes: dataPublishes.current,
},
isPrivate
);
if (responseDataMessage?.error) { if (responseDataMessage?.error) {
const fullObject = { const fullObject = {
@ -201,7 +202,7 @@ export const Thread = ({
try { try {
let threadId = currentThread.threadId; let threadId = currentThread.threadId;
const keyTemp = "thread-post"; const keyTemp = 'thread-post';
const getTempAnnouncements = await getTempPublish(); const getTempAnnouncements = await getTempPublish();
if (getTempAnnouncements?.[keyTemp]) { if (getTempAnnouncements?.[keyTemp]) {
@ -232,10 +233,10 @@ export const Thread = ({
const identifier = `thmsg-${threadId}`; const identifier = `thmsg-${threadId}`;
let url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&prefix=true`; let url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&prefix=true`;
if (!isReverse) { if (!isReverse) {
url = url + "&reverse=false"; url = url + '&reverse=false';
} }
if (isReverse) { if (isReverse) {
url = url + "&reverse=true"; url = url + '&reverse=true';
} }
if (after) { if (after) {
url = url + `&after=${after}`; url = url + `&after=${after}`;
@ -245,9 +246,9 @@ export const Thread = ({
} }
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
const responseData = await response.json(); const responseData = await response.json();
@ -263,14 +264,13 @@ export const Thread = ({
setMessages(fullArrayMsg); setMessages(fullArrayMsg);
if (before === null && after === null && isReverse) { if (before === null && after === null && isReverse) {
setTimeout(() => { setTimeout(() => {
containerRef.current.scrollIntoView({ behavior: "smooth" }); containerRef.current.scrollIntoView({ behavior: 'smooth' });
}, 300); }, 300);
} }
if(after || before === null && after === null && !isReverse){ if (after || (before === null && after === null && !isReverse)) {
setTimeout(() => { setTimeout(() => {
threadBeginningRef.current.scrollIntoView(); threadBeginningRef.current.scrollIntoView();
}, 100); }, 100);
} }
if (fullArrayMsg.length === 0) { if (fullArrayMsg.length === 0) {
@ -282,9 +282,9 @@ export const Thread = ({
fullArrayMsg[0].created fullArrayMsg[0].created
}`; }`;
const responseNewer = await fetch(urlNewer, { const responseNewer = await fetch(urlNewer, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
const responseDataNewer = await responseNewer.json(); const responseDataNewer = await responseNewer.json();
@ -300,9 +300,9 @@ export const Thread = ({
fullArrayMsg[fullArrayMsg.length - 1].created fullArrayMsg[fullArrayMsg.length - 1].created
}`; }`;
const responseOlder = await fetch(urlOlder, { const responseOlder = await fetch(urlOlder, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
const responseDataOlder = await responseOlder.json(); const responseDataOlder = await responseOlder.json();
@ -316,7 +316,7 @@ export const Thread = ({
updateThreadActivityCurrentThread(); updateThreadActivityCurrentThread();
} }
} catch (error) { } catch (error) {
console.log("error", error); console.log('error', error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
getSavedData(groupId); getSavedData(groupId);
@ -325,9 +325,21 @@ export const Thread = ({
[messages, secretKey] [messages, secretKey]
); );
const getMessages = React.useCallback(async () => { 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); 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 firstMount = useRef(false);
const saveTimestamp = useCallback((currentThread: any, username?: string) => { const saveTimestamp = useCallback((currentThread: any, username?: string) => {
@ -339,7 +351,7 @@ export const Thread = ({
return; return;
const threadIdForLocalStorage = `qmail_threads_${currentThread?.threadData?.groupId}_${currentThread?.threadId}`; const threadIdForLocalStorage = `qmail_threads_${currentThread?.threadData?.groupId}_${currentThread?.threadId}`;
const threads = JSON.parse( 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 // Convert to an array of objects with identifier and all fields
let dataArray = Object.entries(threads).map(([identifier, value]) => ({ let dataArray = Object.entries(threads).map(([identifier, value]) => ({
@ -382,8 +394,8 @@ export const Thread = ({
if (currentThreadRef.current?.threadId !== currentThread?.threadId) { if (currentThreadRef.current?.threadId !== currentThread?.threadId) {
firstMount.current = false; firstMount.current = false;
} }
if(!secretKey && isPrivate) return if (!secretKey && isPrivate) return;
if (currentThread && !firstMount.current && isPrivate !== null) { if (currentThread && !firstMount.current && isPrivate !== null) {
getMessagesMiddleware(); getMessagesMiddleware();
} }
}, [currentThread, secretKey, isPrivate]); }, [currentThread, secretKey, isPrivate]);
@ -402,9 +414,9 @@ export const Thread = ({
const identifier = `thmsg-${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 url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
const responseData = await response.json(); const responseData = await response.json();
@ -469,17 +481,17 @@ export const Thread = ({
const threadFetchModeFunc = (e) => { const threadFetchModeFunc = (e) => {
const mode = e.detail?.mode; const mode = e.detail?.mode;
if (mode === "last-page") { if (mode === 'last-page') {
getMailMessages(currentThread, null, null, true, groupInfo?.groupId); getMailMessages(currentThread, null, null, true, groupInfo?.groupId);
} }
firstMount.current = true; firstMount.current = true;
}; };
React.useEffect(() => { React.useEffect(() => {
subscribeToEvent("threadFetchMode", threadFetchModeFunc); subscribeToEvent('threadFetchMode', threadFetchModeFunc);
return () => { return () => {
unsubscribeFromEvent("threadFetchMode", threadFetchModeFunc); unsubscribeFromEvent('threadFetchMode', threadFetchModeFunc);
}; };
}, []); }, []);
@ -526,11 +538,11 @@ export const Thread = ({
handleScroll(); handleScroll();
}, 400); }, 400);
container.addEventListener("scroll", handleScroll); container.addEventListener('scroll', handleScroll);
// Cleanup // Cleanup
return () => { return () => {
container.removeEventListener("scroll", handleScroll); container.removeEventListener('scroll', handleScroll);
}; };
}, [messages]); }, [messages]);
@ -540,9 +552,9 @@ export const Thread = ({
if (!container) return; if (!container) return;
if (isAtBottom) { if (isAtBottom) {
container.scrollTo({ top: 0, behavior: "smooth" }); // Scroll to top container.scrollTo({ top: 0, behavior: 'smooth' }); // Scroll to top
} else { } 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 ( return (
<GroupContainer <GroupContainer
sx={{ sx={{
position: "relative", position: 'relative',
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
overflow: "hidden", overflow: 'hidden',
}} }}
// Removed the ref from here since the scrollable area has changed // Removed the ref from here since the scrollable area has changed
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
justifyContent: "space-between", justifyContent: 'space-between',
alignItems: "center", alignItems: 'center',
flexShrink: 0, // Corrected property name flexShrink: 0, // Corrected property name
}} }}
> >
@ -583,16 +595,16 @@ export const Thread = ({
/> />
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
gap: isMobile ? "45px" : "35px", gap: isMobile ? '45px' : '35px',
alignItems: "center", alignItems: 'center',
padding: isMobile && "5px", padding: isMobile && '5px',
}} }}
> >
<ShowMessageReturnButton <ShowMessageReturnButton
sx={{ sx={{
padding: isMobile && "5px", padding: isMobile && '5px',
minWidth: isMobile && "50px", minWidth: isMobile && '50px',
}} }}
onClick={() => { onClick={() => {
setMessages([]); setMessages([]);
@ -608,9 +620,9 @@ export const Thread = ({
<ButtonBase onClick={scrollToPosition}> <ButtonBase onClick={scrollToPosition}>
<ArrowUpwardIcon <ArrowUpwardIcon
sx={{ sx={{
color: "white", color: 'white',
cursor: "pointer", cursor: 'pointer',
fontSize: isMobile ? "28px" : "36px", fontSize: isMobile ? '28px' : '36px',
}} }}
/> />
</ButtonBase> </ButtonBase>
@ -618,9 +630,9 @@ export const Thread = ({
<ButtonBase onClick={scrollToPosition}> <ButtonBase onClick={scrollToPosition}>
<ArrowDownwardIcon <ArrowDownwardIcon
sx={{ sx={{
color: "white", color: 'white',
cursor: "pointer", cursor: 'pointer',
fontSize: isMobile ? "28px" : "36px", fontSize: isMobile ? '28px' : '36px',
}} }}
/> />
</ButtonBase> </ButtonBase>
@ -631,45 +643,45 @@ export const Thread = ({
<ThreadContainerFullWidth <ThreadContainerFullWidth
sx={{ sx={{
flexGrow: 1, flexGrow: 1,
overflow: "auto", overflow: 'auto',
}} }}
ref={threadContainerRef} // Updated ref attached here ref={threadContainerRef} // Updated ref attached here
> >
<div ref={threadBeginningRef}/> <div ref={threadBeginningRef} />
<ThreadContainer> <ThreadContainer>
<Spacer height={isMobile ? "10px" : "30px"} /> <Spacer height={isMobile ? '10px' : '30px'} />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
alignItems: "center", alignItems: 'center',
display: "flex", display: 'flex',
justifyContent: "space-between", justifyContent: 'space-between',
}} }}
> >
<GroupNameP <GroupNameP
sx={{ sx={{
fontSize: isMobile && "18px", fontSize: isMobile && '18px',
}} }}
> >
{currentThread?.threadData?.title} {currentThread?.threadData?.title}
</GroupNameP> </GroupNameP>
</Box> </Box>
<Spacer height={"15px"} /> <Spacer height={'15px'} />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
alignItems: "center", alignItems: 'center',
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
gap: "5px", gap: '5px',
}} }}
> >
<Button <Button
sx={{ sx={{
padding: isMobile && "5px", padding: isMobile && '5px',
fontSize: isMobile && "14px", fontSize: isMobile && '14px',
textTransformation: "capitalize", textTransformation: 'capitalize',
}} }}
onClick={() => { onClick={() => {
getMailMessages( getMailMessages(
@ -687,9 +699,9 @@ export const Thread = ({
</Button> </Button>
<Button <Button
sx={{ sx={{
padding: isMobile && "5px", padding: isMobile && '5px',
fontSize: isMobile && "14px", fontSize: isMobile && '14px',
textTransformation: "capitalize", textTransformation: 'capitalize',
}} }}
onClick={() => { onClick={() => {
getMailMessages( getMailMessages(
@ -707,9 +719,9 @@ export const Thread = ({
</Button> </Button>
<Button <Button
sx={{ sx={{
padding: isMobile && "5px", padding: isMobile && '5px',
fontSize: isMobile && "14px", fontSize: isMobile && '14px',
textTransformation: "capitalize", textTransformation: 'capitalize',
}} }}
onClick={() => { onClick={() => {
getMailMessages( getMailMessages(
@ -727,9 +739,9 @@ export const Thread = ({
</Button> </Button>
<Button <Button
sx={{ sx={{
padding: isMobile && "5px", padding: isMobile && '5px',
fontSize: isMobile && "14px", fontSize: isMobile && '14px',
textTransformation: "capitalize", textTransformation: 'capitalize',
}} }}
onClick={() => { onClick={() => {
getMailMessages( getMailMessages(
@ -746,35 +758,34 @@ export const Thread = ({
Last Last
</Button> </Button>
</Box> </Box>
<Spacer height={isMobile ? "10px" : "30px"} /> <Spacer height={isMobile ? '10px' : '30px'} />
{combinedListTempAndReal.map((message, index, list) => { {combinedListTempAndReal.map((message, index, list) => {
let fullMessage = message; let fullMessage = message;
if (hashMapMailMessages[message?.identifier]) { if (hashMapMailMessages[message?.identifier]) {
fullMessage = hashMapMailMessages[message.identifier]; fullMessage = hashMapMailMessages[message.identifier];
if (fullMessage?.error) { if (fullMessage?.error) {
return ( return (
<SingleThreadParent <SingleThreadParent
sx={{ sx={{
height: "auto", height: 'auto',
}} }}
> >
<Box <Box
style={{ style={{
width: "100%", width: '100%',
borderRadius: "8px", borderRadius: '8px',
overflow: "hidden", overflow: 'hidden',
position: "relative", position: 'relative',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
alignItems: "flex-start", alignItems: 'flex-start',
gap: "10px", gap: '10px',
}} }}
> >
<WrapperUserAction <WrapperUserAction
@ -784,8 +795,8 @@ export const Thread = ({
> >
<Avatar <Avatar
sx={{ sx={{
height: "50px", height: '50px',
width: "50px", width: '50px',
}} }}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
message?.name message?.name
@ -812,23 +823,22 @@ export const Thread = ({
</Box> </Box>
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "18px", fontSize: '18px',
color: "white", color: 'white',
}} }}
> >
{fullMessage?.error} {fullMessage?.error}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
</SingleThreadParent> </SingleThreadParent>
); );
@ -855,23 +865,23 @@ export const Thread = ({
return ( return (
<SingleThreadParent <SingleThreadParent
sx={{ sx={{
height: "auto", height: 'auto',
}} }}
> >
<Box <Box
style={{ style={{
width: "100%", width: '100%',
borderRadius: "8px", borderRadius: '8px',
overflow: "hidden", overflow: 'hidden',
position: "relative", position: 'relative',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
alignItems: "flex-start", alignItems: 'flex-start',
gap: "10px", gap: '10px',
}} }}
> >
<WrapperUserAction <WrapperUserAction
@ -881,8 +891,8 @@ export const Thread = ({
> >
<Avatar <Avatar
sx={{ sx={{
height: "50px", height: '50px',
width: "50px", width: '50px',
}} }}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
message?.name message?.name
@ -909,37 +919,36 @@ export const Thread = ({
</Box> </Box>
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<CustomLoader /> <CustomLoader />
<Typography <Typography
sx={{ sx={{
fontSize: "18px", fontSize: '18px',
color: "white", color: 'white',
}} }}
> >
Downloading from QDN Downloading from QDN
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
</SingleThreadParent> </SingleThreadParent>
); );
})} })}
{!hasLastPage && !isLoading && ( {!hasLastPage && !isLoading && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
justifyContent: "flex-end", justifyContent: 'flex-end',
}} }}
> >
<Button <Button
@ -955,7 +964,7 @@ export const Thread = ({
); );
}} }}
sx={{ sx={{
color: "white", color: 'white',
}} }}
> >
Refetch page Refetch page
@ -964,112 +973,113 @@ export const Thread = ({
</> </>
)} )}
<Box
<Box sx={{ sx={{
width: '100%', width: '100%',
visibility: messages?.length > 4 ? 'visible' : 'hidden' visibility: messages?.length > 4 ? 'visible' : 'hidden',
}}> }}
<Spacer height="30px" /> >
<Box <Spacer height="30px" />
<Box
sx={{
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
gap: '5px',
}}
>
<Button
sx={{ sx={{
width: "100%", padding: isMobile && '5px',
alignItems: "center", fontSize: isMobile && '14px',
display: "flex", textTransformation: 'capitalize',
justifyContent: "center",
gap: "5px",
}} }}
onClick={() => {
getMailMessages(
currentThread,
null,
null,
false,
groupInfo?.groupId
);
}}
disabled={!hasFirstPage}
variant="contained"
> >
<Button First
sx={{ </Button>
padding: isMobile && "5px", <Button
fontSize: isMobile && "14px", sx={{
textTransformation: "capitalize", padding: isMobile && '5px',
}} fontSize: isMobile && '14px',
onClick={() => { textTransformation: 'capitalize',
getMailMessages( }}
currentThread, onClick={() => {
null, getMailMessages(
null, currentThread,
false, messages[0].created,
groupInfo?.groupId null,
); false,
}} groupInfo?.groupId
disabled={!hasFirstPage} );
variant="contained" }}
> disabled={!hasPreviousPage}
First variant="contained"
</Button> >
<Button Previous
sx={{ </Button>
padding: isMobile && "5px", <Button
fontSize: isMobile && "14px", sx={{
textTransformation: "capitalize", padding: isMobile && '5px',
}} fontSize: isMobile && '14px',
onClick={() => { textTransformation: 'capitalize',
getMailMessages( }}
currentThread, onClick={() => {
messages[0].created, getMailMessages(
null, currentThread,
false, null,
groupInfo?.groupId messages[messages.length - 1].created,
); false,
}} groupInfo?.groupId
disabled={!hasPreviousPage} );
variant="contained" }}
> disabled={!hasNextPage}
Previous variant="contained"
</Button> >
<Button Next
sx={{ </Button>
padding: isMobile && "5px", <Button
fontSize: isMobile && "14px", sx={{
textTransformation: "capitalize", padding: isMobile && '5px',
}} fontSize: isMobile && '14px',
onClick={() => { textTransformation: 'capitalize',
getMailMessages( }}
currentThread, onClick={() => {
null, getMailMessages(
messages[messages.length - 1].created, currentThread,
false, null,
groupInfo?.groupId null,
); true,
}} groupInfo?.groupId
disabled={!hasNextPage} );
variant="contained" }}
> disabled={!hasLastPage}
Next variant="contained"
</Button> >
<Button Last
sx={{ </Button>
padding: isMobile && "5px",
fontSize: isMobile && "14px",
textTransformation: "capitalize",
}}
onClick={() => {
getMailMessages(
currentThread,
null,
null,
true,
groupInfo?.groupId
);
}}
disabled={!hasLastPage}
variant="contained"
>
Last
</Button>
</Box>
<Spacer height="30px" />
</Box> </Box>
<Spacer height="30px" />
<div ref={containerRef} /> </Box>
<div ref={containerRef} />
</ThreadContainer> </ThreadContainer>
</ThreadContainerFullWidth> </ThreadContainerFullWidth>
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: "Loading posts... please wait.", message: 'Loading posts... please wait.',
}} }}
/> />
</GroupContainer> </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)=> { const isUserBlocked = useCallback((address, name)=> {
try { try {
if(!address) return false if(!address) return false
if(userBlockedRef.current[address] || userNamesBlockedRef.current[name]) return true if(userBlockedRef.current[address]) return true
return false return false
@ -90,43 +90,13 @@ export const useBlockedAddresses = () => {
}, []) }, [])
const removeBlockFromList = useCallback(async (address, name)=> { const removeBlockFromList = useCallback(async (address, name)=> {
await new Promise((res, rej) => { if(name){
window.sendMessage("listActions", {
type: 'remove',
items: name ? [name] : [address],
listName: name ? 'blockedNames' : 'blockedAddresses'
})
.then((response) => {
if (response.error) {
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);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
if(name && userBlockedRef.current[address]){
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("listActions", { window.sendMessage("listActions", {
type: 'remove', type: 'remove',
items: !name ? [name] : [address], items: [name] ,
listName: !name ? 'blockedNames' : 'blockedAddresses' listName: 'blockedNames'
}) })
.then((response) => { .then((response) => {
@ -134,9 +104,12 @@ export const useBlockedAddresses = () => {
rej(response?.message); rej(response?.message);
return; return;
} else { } else {
const copyObject = {...userBlockedRef.current}
delete copyObject[address] const copyObject = {...userNamesBlockedRef.current}
userBlockedRef.current = copyObject delete copyObject[name]
userNamesBlockedRef.current = copyObject
res(response); res(response);
} }
}) })
@ -145,42 +118,95 @@ export const useBlockedAddresses = () => {
}); });
}) })
} }
if(address){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: [address],
listName: 'blockedAddresses'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
}
}, []) }, [])
const addToBlockList = useCallback(async (address, name)=> { const addToBlockList = useCallback(async (address, name)=> {
await new Promise((res, rej) => { if(name){
window.sendMessage("listActions", { await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'add',
items: name ? [name] : [address],
listName: name ? 'blockedNames' : 'blockedAddresses'
})
.then((response) => {
if (response.error) {
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); type: 'add',
} items: [name],
listName: 'blockedNames'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userNamesBlockedRef.current}
copyObject[name] = true
userNamesBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
}) })
.catch((error) => { }
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 { return {

View File

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

View File

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

View File

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

View File

@ -1,167 +1,170 @@
import { Box, CircularProgress } from '@mui/material'; import { Box, CircularProgress } from '@mui/material';
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react';
import { CustomButton, CustomInput, CustomLabel, TextP } from '../App-styles'; import {
CustomButton,
CustomInput,
CustomLabel,
TextP,
} from '../styles/App-styles';
import { Spacer } from '../common/Spacer'; import { Spacer } from '../common/Spacer';
import BoundedNumericTextField from '../common/BoundedNumericTextField'; import BoundedNumericTextField from '../common/BoundedNumericTextField';
import { PasswordField } from './PasswordField/PasswordField'; import { PasswordField } from './PasswordField/PasswordField';
import { ErrorText } from './ErrorText/ErrorText'; import { ErrorText } from './ErrorText/ErrorText';
import { getFee } from '../background'; 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 [paymentTo, setPaymentTo] = useState<string>(defaultPaymentTo);
const [paymentAmount, setPaymentAmount] = useState<number>(0); const [paymentAmount, setPaymentAmount] = useState<number>(0);
const [paymentPassword, setPaymentPassword] = useState<string>(""); const [paymentPassword, setPaymentPassword] = useState<string>('');
const [sendPaymentError, setSendPaymentError] = useState<string>(""); const [sendPaymentError, setSendPaymentError] = useState<string>('');
const [sendPaymentSuccess, setSendPaymentSuccess] = useState<string>(""); const [sendPaymentSuccess, setSendPaymentSuccess] = useState<string>('');
const [isLoadingSendCoin, setIsLoadingSendCoin] = useState<boolean>(false); const [isLoadingSendCoin, setIsLoadingSendCoin] = useState<boolean>(false);
const sendCoinFunc = async () => {
try {
setSendPaymentError('');
setSendPaymentSuccess('');
if (!paymentTo) {
setSendPaymentError('Please enter a recipient');
return;
}
if (!paymentAmount) {
setSendPaymentError('Please enter an amount greater than 0');
return;
}
if (!paymentPassword) {
setSendPaymentError('Please enter your wallet password');
return;
}
const fee = await getFee('PAYMENT');
await show({
const sendCoinFunc = async() => { message: `Would you like to transfer ${Number(paymentAmount)} QORT?`,
try { paymentFee: fee.fee + ' QORT',
setSendPaymentError(""); });
setSendPaymentSuccess(""); setIsLoadingSendCoin(true);
if (!paymentTo) { window
setSendPaymentError("Please enter a recipient"); .sendMessage('sendCoin', {
return; amount: Number(paymentAmount),
receiver: paymentTo.trim(),
password: paymentPassword,
})
.then((response) => {
if (response?.error) {
setSendPaymentError(response.error);
} else {
onSuccess();
} }
if (!paymentAmount) { setIsLoadingSendCoin(false);
setSendPaymentError("Please enter an amount greater than 0"); })
return; .catch((error) => {
} console.error('Failed to send coin:', error);
if (!paymentPassword) { setIsLoadingSendCoin(false);
setSendPaymentError("Please enter your wallet password"); });
return; } catch (error) {
} // error
const fee = await getFee('PAYMENT') }
};
await show({
message: `Would you like to transfer ${Number(paymentAmount)} QORT?` ,
paymentFee: fee.fee + ' QORT'
})
setIsLoadingSendCoin(true);
window
.sendMessage("sendCoin", {
amount: Number(paymentAmount),
receiver: paymentTo.trim(),
password: paymentPassword,
})
.then((response) => {
if (response?.error) {
setSendPaymentError(response.error);
} else {
onSuccess()
}
setIsLoadingSendCoin(false);
})
.catch((error) => {
console.error("Failed to send coin:", error);
setIsLoadingSendCoin(false);
});
} catch (error) {
// error
}
};
return ( return (
<> <>
<Box <Box
sx={{ sx={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "flex-start", alignItems: 'flex-start',
}} }}
> >
<TextP <TextP
sx={{ sx={{
textAlign: "start", textAlign: 'start',
lineHeight: "24px", lineHeight: '24px',
fontSize: "20px", fontSize: '20px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
Transfer QORT Transfer QORT
</TextP> </TextP>
<Spacer height="35px" /> <Spacer height="35px" />
<TextP <TextP
sx={{ sx={{
textAlign: "start", textAlign: 'start',
lineHeight: "16px", lineHeight: '16px',
fontSize: "20px", fontSize: '20px',
fontWeight: 600, fontWeight: 600,
color: "rgba(255, 255, 255, 0.5)", color: 'rgba(255, 255, 255, 0.5)',
}} }}
> >
Balance: Balance:
</TextP> </TextP>
<TextP <TextP
sx={{ sx={{
textAlign: "start", textAlign: 'start',
lineHeight: "24px", lineHeight: '24px',
fontSize: "20px", fontSize: '20px',
fontWeight: 700, fontWeight: 700,
}} }}
> >
{balance?.toFixed(2)} QORT {balance?.toFixed(2)} QORT
</TextP> </TextP>
</Box> </Box>
<Spacer height="35px" /> <Spacer height="35px" />
<Box> <Box>
<CustomLabel htmlFor="standard-adornment-name">To</CustomLabel> <CustomLabel htmlFor="standard-adornment-name">To</CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />
<CustomInput <CustomInput
id="standard-adornment-name" id="standard-adornment-name"
value={paymentTo} value={paymentTo}
onChange={(e) => setPaymentTo(e.target.value)} onChange={(e) => setPaymentTo(e.target.value)}
autoComplete="off" autoComplete="off"
/> />
<Spacer height="6px" /> <Spacer height="6px" />
<CustomLabel htmlFor="standard-adornment-amount"> <CustomLabel htmlFor="standard-adornment-amount">Amount</CustomLabel>
Amount <Spacer height="5px" />
</CustomLabel> <BoundedNumericTextField
<Spacer height="5px" /> value={paymentAmount}
<BoundedNumericTextField minValue={0}
value={paymentAmount} maxValue={+balance}
minValue={0} allowDecimals={true}
maxValue={+balance} initialValue={'0'}
allowDecimals={true} allowNegatives={false}
initialValue={'0'} afterChange={(e: string) => setPaymentAmount(+e)}
allowNegatives={false} />
afterChange={(e: string) => setPaymentAmount(+e)} <Spacer height="6px" />
/> <CustomLabel htmlFor="standard-adornment-password">
<Spacer height="6px" /> Confirm Wallet Password
<CustomLabel htmlFor="standard-adornment-password"> </CustomLabel>
Confirm Wallet Password <Spacer height="5px" />
</CustomLabel> <PasswordField
<Spacer height="5px" /> id="standard-adornment-password"
<PasswordField value={paymentPassword}
id="standard-adornment-password" onChange={(e) => setPaymentPassword(e.target.value)}
value={paymentPassword} autoComplete="off"
onChange={(e) => setPaymentPassword(e.target.value)} />
autoComplete="off" </Box>
/> <Spacer height="10px" />
</Box> <ErrorText>{sendPaymentError}</ErrorText>
<Spacer height="10px" /> {/* <Typography>{sendPaymentSuccess}</Typography> */}
<ErrorText>{sendPaymentError}</ErrorText> <Spacer height="25px" />
{/* <Typography>{sendPaymentSuccess}</Typography> */} <CustomButton
<Spacer height="25px" /> sx={{
<CustomButton cursor: isLoadingSendCoin ? 'default' : 'pointer',
}}
onClick={() => {
if (isLoadingSendCoin) return;
sendCoinFunc();
}}
>
{isLoadingSendCoin && (
<CircularProgress
size={16}
sx={{ sx={{
cursor: isLoadingSendCoin ? 'default' : 'pointer' color: 'white',
}} }}
onClick={() => { />
if(isLoadingSendCoin) return )}
sendCoinFunc(); Send
}} </CustomButton>
>
{isLoadingSendCoin && (
<CircularProgress size={16} sx={{
color: 'white'
}} />
)}
Send
</CustomButton>
</> </>
) );
} };

View File

@ -1,24 +1,19 @@
import { createContext, useContext, useState, useMemo } from 'react'; 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({ const ThemeContext = createContext({
palette: { themeMode: 'light',
mode: 'dark', toggleTheme: () => {},
},
}); });
const lightTheme = createTheme({
palette: {
mode: 'light',
},
});
const ThemeContext = createContext({ themeMode: 'light', toggleTheme: () => {} });
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [themeMode, setThemeMode] = useState('light'); const [themeMode, setThemeMode] = useState('light');
const theme = useMemo(() => (themeMode === 'light' ? lightTheme : darkTheme), [themeMode]); const theme = useMemo(
() => (themeMode === 'light' ? lightTheme : darkTheme),
[themeMode]
);
const toggleTheme = () => { const toggleTheme = () => {
setThemeMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')); setThemeMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
@ -31,4 +26,4 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
); );
}; };
export const useThemeContext = () => useContext(ThemeContext); export const useThemeContext = () => useContext(ThemeContext);

View File

@ -1,58 +1,58 @@
import { useThemeContext } from "./ThemeContext"; import { useThemeContext } from './ThemeContext';
import { styled, Switch } from "@mui/material"; import { styled, Switch } from '@mui/material';
const ThemeSwitch = styled(Switch)(({ theme }) => ({ const ThemeSwitch = styled(Switch)(({ theme }) => ({
width: 62, width: 62,
height: 34, height: 34,
padding: 7, padding: 7,
"& .MuiSwitch-switchBase": { '& .MuiSwitch-switchBase': {
margin: 1, margin: 1,
padding: 0, padding: 0,
transform: "translateX(6px)", transform: 'translateX(6px)',
"&.Mui-checked": { '&.Mui-checked': {
color: "#fff", color: '#fff',
transform: "translateX(22px)", transform: 'translateX(22px)',
"& .MuiSwitch-thumb:before": { '& .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( 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>')`, )}" 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, opacity: 1,
backgroundColor: "#aab4be", backgroundColor: '#aab4be',
...theme.applyStyles("dark", { ...theme.applyStyles('dark', {
backgroundColor: "#8796A5", backgroundColor: '#8796A5',
}), }),
}, },
}, },
}, },
"& .MuiSwitch-thumb": { '& .MuiSwitch-thumb': {
backgroundColor: "#001e3c", backgroundColor: '#fde402',
width: 32, width: 32,
height: 32, height: 32,
"&::before": { '&::before': {
content: "''", content: "''",
position: "absolute", position: 'absolute',
width: "100%", width: '100%',
height: "100%", height: '100%',
left: 0, left: 0,
top: 0, top: 0,
backgroundRepeat: "no-repeat", backgroundRepeat: 'no-repeat',
backgroundPosition: "center", 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( 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>')`, )}" 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", { ...theme.applyStyles('dark', {
backgroundColor: "#003892", backgroundColor: '#003892',
}), }),
}, },
"& .MuiSwitch-track": { '& .MuiSwitch-track': {
opacity: 1, opacity: 1,
backgroundColor: "#aab4be", backgroundColor: '#aab4be',
borderRadius: 20 / 2, borderRadius: 20 / 2,
...theme.applyStyles("dark", { ...theme.applyStyles('dark', {
backgroundColor: "#8796A5", backgroundColor: '#8796A5',
}), }),
}, },
})); }));
@ -62,14 +62,14 @@ const ThemeSelector = ({ style }) => {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
gap: "1px", gap: '1px',
...style, ...style,
}} }}
> >
<ThemeSwitch checked={themeMode === "dark"} onChange={toggleTheme} /> <ThemeSwitch checked={themeMode === 'dark'} onChange={toggleTheme} />
</div> </div>
); );
}; };

View File

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

View File

@ -1,13 +1,12 @@
import React from "react"; import ReactDOM from 'react-dom/client';
import ReactDOM from "react-dom/client"; import App from './App.tsx';
import App from "./App.tsx"; import '../src/styles/index.css';
import "./index.css"; import './messaging/messagesToBackground';
import "./messaging/messagesToBackground"; import { MessageQueueProvider } from './MessageQueueContext.tsx';
import { MessageQueueProvider } from "./MessageQueueContext.tsx"; import { RecoilRoot } from 'recoil';
import { RecoilRoot } from "recoil"; import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
import { ThemeProvider } from "./components/Theme/ThemeContext.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<> <>
<ThemeProvider> <ThemeProvider>
<MessageQueueProvider> <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 { .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; 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); 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; opacity: 0;
padding: 10px 10px;
position: absolute;
right: 0px;
top: 35px;
transition: opacity 0.2s; 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 { .tooltip:hover .bottom {
@ -47,13 +54,13 @@
} }
.tooltip .bottom i::after { .tooltip .bottom i::after {
content: "";
position: absolute;
width: 12px;
height: 12px;
left: 50%;
transform: translate(-50%, 50%) rotate(45deg);
background-color: var(--white); background-color: var(--white);
border: 1px solid var(--black); border: 1px solid var(--black);
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5); 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-face {
font-family: "Inter"; font-family: 'Inter';
src: url("./styles/fonts/Inter-SemiBold.ttf") format("truetype"); src: url('./styles/fonts/Inter-SemiBold.ttf') format('truetype');
font-weight: 600; font-weight: 600;
} }
@font-face { @font-face {
font-family: "Inter"; font-family: 'Inter';
src: url("./styles/fonts/Inter-ExtraBold.ttf") format("truetype"); src: url('./styles/fonts/Inter-ExtraBold.ttf') format('truetype');
font-weight: 800; font-weight: 800;
} }
@font-face { @font-face {
font-family: "Inter"; font-family: 'Inter';
src: url("./styles/fonts/Inter-Bold.ttf") format("truetype"); src: url('./styles/fonts/Inter-Bold.ttf') format('truetype');
font-weight: 700; font-weight: 700;
} }
@font-face { @font-face {
font-family: "Inter"; font-family: 'Inter';
src: url("./styles/fonts/Inter-Regular.ttf") format("truetype"); src: url('./styles/fonts/Inter-Regular.ttf') format('truetype');
font-weight: 400; font-weight: 400;
} }
@ -89,7 +89,7 @@ body {
} }
@property --var1 { @property --var1 {
syntax: "<color>"; syntax: '<color>';
inherits: true; inherits: true;
initial-value: transparent; 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 // Extend the Theme interface
const commonThemeOptions = { const commonThemeOptions = {
typography: { typography: {
fontFamily: [ fontFamily: ['Roboto'].join(','),
'Roboto'
].join(','),
h1: { h1: {
fontSize: '2rem', fontSize: '2rem',
fontWeight: 600 fontWeight: 600,
}, },
h2: { h2: {
fontSize: '1.75rem', fontSize: '1.75rem',
fontWeight: 500 fontWeight: 500,
}, },
h3: { h3: {
fontSize: '1.5rem', fontSize: '1.5rem',
fontWeight: 500 fontWeight: 500,
}, },
h4: { h4: {
fontSize: '1.25rem', fontSize: '1.25rem',
fontWeight: 500 fontWeight: 500,
}, },
h5: { h5: {
fontSize: '1rem', fontSize: '1rem',
fontWeight: 500 fontWeight: 500,
}, },
h6: { h6: {
fontSize: '0.875rem', fontSize: '0.875rem',
fontWeight: 500 fontWeight: 500,
}, },
body1: { body1: {
fontSize: '23px', fontSize: '23px',
fontWeight: 400, fontWeight: 400,
lineHeight: 1.5, lineHeight: 1.5,
letterSpacing: '0.5px' letterSpacing: 'normal',
}, },
body2: { body2: {
fontSize: '18px', fontSize: '18px',
fontWeight: 400, fontWeight: 400,
lineHeight: 1.4, lineHeight: 1.4,
letterSpacing: '0.2px' letterSpacing: '0.2px',
} },
}, },
spacing: 8, spacing: 8,
shape: { shape: {
borderRadius: 4 borderRadius: 4,
}, },
breakpoints: { breakpoints: {
values: { values: {
@ -56,8 +52,8 @@ const commonThemeOptions = {
sm: 600, sm: 600,
md: 900, md: 900,
lg: 1200, lg: 1200,
xl: 1536 xl: 1536,
} },
}, },
components: { components: {
MuiButton: { MuiButton: {
@ -66,25 +62,24 @@ const commonThemeOptions = {
backgroundColor: 'inherit', backgroundColor: 'inherit',
transition: 'filter 0.3s ease-in-out', transition: 'filter 0.3s ease-in-out',
'&:hover': { '&:hover': {
filter: 'brightness(1.1)' filter: 'brightness(1.1)',
} },
} },
}, },
defaultProps: { defaultProps: {
disableElevation: true, disableElevation: true,
disableRipple: true disableRipple: true,
} },
}, },
MuiModal: { MuiModal: {
styleOverrides: { styleOverrides: {
root: { root: {
zIndex: 50000, zIndex: 50000,
}, },
} },
},
} },
} };
}
const lightTheme = createTheme({ const lightTheme = createTheme({
...commonThemeOptions, ...commonThemeOptions,
@ -93,46 +88,46 @@ const lightTheme = createTheme({
primary: { primary: {
main: '#f4f4fb', main: '#f4f4fb',
dark: '#eaecf4', dark: '#eaecf4',
light: '#f9f9fd' light: '#f9f9fd',
}, },
secondary: { secondary: {
main: '#1EAAF1' main: '#c2deec',
}, },
background: { background: {
default: '#fafafa', default: '#fafafa',
paper: '#f0f0f0' paper: '#f0f0f0',
}, },
text: { text: {
primary: '#000000', primary: '#000000',
secondary: '#525252' secondary: '#525252',
} },
}, },
components: { components: {
MuiCard: { MuiCard: {
styleOverrides: { styleOverrides: {
root: { root: {
boxShadow: 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', borderRadius: '8px',
transition: 'all 0.3s ease-in-out', transition: 'all 0.3s ease-in-out',
'&:hover': { '&:hover': {
cursor: 'pointer', cursor: 'pointer',
boxShadow: 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: { MuiIcon: {
defaultProps: { defaultProps: {
style: { style: {
color: '#000000' color: '#000000',
} opacity: 0.5,
} },
} },
},
}, },
}) });
const darkTheme = createTheme({ const darkTheme = createTheme({
...commonThemeOptions, ...commonThemeOptions,
@ -140,45 +135,46 @@ const darkTheme = createTheme({
mode: 'dark', mode: 'dark',
primary: { primary: {
main: '#2e3d60', main: '#2e3d60',
dark: "#1a2744", dark: '#1a2744',
light: "#3f4b66", light: '#3f4b66',
}, },
secondary: { secondary: {
main: '#45adff' main: '#45adff',
}, },
background: { background: {
default: '#313338', default: '#313338',
paper: "#1e1e20" paper: '#1e1e20',
}, },
text: { text: {
primary: '#ffffff', primary: '#ffffff',
secondary: '#b3b3b3' secondary: '#b3b3b3',
} },
}, },
components: { components: {
MuiCard: { MuiCard: {
styleOverrides: { styleOverrides: {
root: { root: {
boxShadow: "none", boxShadow: 'none',
borderRadius: '8px', borderRadius: '8px',
transition: 'all 0.3s ease-in-out', transition: 'all 0.3s ease-in-out',
'&:hover': { '&:hover': {
cursor: 'pointer', cursor: 'pointer',
boxShadow: 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: { MuiIcon: {
defaultProps: { defaultProps: {
style: { style: {
color: '#ffffff' color: '#ffffff',
} opacity: 0.5,
} },
} },
},
}, },
}) });
export { lightTheme, darkTheme } export { lightTheme, darkTheme };