Refactor sReturn icon and set theme styles

This commit is contained in:
Nicola Benaglia 2025-04-12 15:31:10 +02:00
parent c5cfaf3722
commit dcfa7b258e
13 changed files with 3712 additions and 3303 deletions

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 './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={
<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>
title={ <HtmlTooltip
<React.Fragment> disableHoverListener={hasSeenGettingStarted === true}
<Typography color="inherit" sx={{ title={
fontSize: '16px' <React.Fragment>
}}>Use this option to connect additional Qortal wallets you've already made, in order to login with them afterwards. You will need access to your backup JSON file in order to do so.</Typography> <Typography
</React.Fragment> color="inherit"
} sx={{
> fontSize: '16px',
<CustomButton sx={{ }}
padding: '10px' >
}} {...getRootProps()}> Use this option to connect additional Qortal wallets you've
<input {...getInputProps()} /> already made, in order to login with them afterwards. You will
Add account need access to your backup JSON file in order to do so.
</CustomButton> </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
sx={{
display: "flex",
flexDirection: "column",
}} <DialogContent>
> <Box
sx={{
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',
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,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,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,9 +1,10 @@
import { useTheme } from "@mui/material"; import { useTheme } from '@mui/material';
// TODO: extend interface
export const SaveIcon = ({ color }) => { export const SaveIcon = ({ color }) => {
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

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,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,95 @@ 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')({ 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 AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => { export const AppsLibrary = ({
const [searchValue, setSearchValue] = useState(""); 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 +135,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 +168,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 +178,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 +187,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 +215,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 +230,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 +245,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 +260,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 +277,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 // 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',
}}> }}
{categories?.map((category)=> { >
return ( {categories?.map((category) => {
<ButtonBase key={category?.id} onClick={()=> { return (
executeEvent('selectedCategory', { <ButtonBase
data: category key={category?.id}
}) onClick={() => {
}}> executeEvent('selectedCategory', {
<Box sx={{ data: category,
display: 'flex', });
alignItems: 'center', }}
justifyContent: 'center', >
height: '110px', <Box
width: '110px', sx={{
background: 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)', display: 'flex',
color: '#1D1D1E', alignItems: 'center',
fontWeight: 700, justifyContent: 'center',
fontSize: '16px', height: '110px',
flexShrink: 0, width: '110px',
borderRadius: '11px' background:
}}> 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
{category?.name} color: '#1D1D1E',
</Box> fontWeight: 700,
</ButtonBase> 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,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

@ -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 ||
@ -93,14 +88,17 @@ 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,24 +919,23 @@ 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>
); );
@ -937,9 +946,9 @@ export const Thread = ({
<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" />
</Box>
<div ref={containerRef} /> <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>

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
@ -70,7 +73,10 @@ export const PasswordField = forwardRef<HTMLInputElement, TextFieldProps>(
> >
<VisibilityOffIcon <VisibilityOffIcon
sx={{ sx={{
color: "white", color:
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.3)',
}} }}
/> />
</ButtonBase> </ButtonBase>
@ -81,7 +87,10 @@ export const PasswordField = forwardRef<HTMLInputElement, TextFieldProps>(
> >
<VisibilityIcon <VisibilityIcon
sx={{ sx={{
color: "white", color:
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.3)',
}} }}
/> />
</ButtonBase> </ButtonBase>