Merge branch 'develop' into feature/asset-qortalrequests

This commit is contained in:
2025-05-01 12:31:21 +03:00
312 changed files with 38734 additions and 35336 deletions

View File

@@ -1,229 +0,0 @@
import {
AppBar,
Button,
Toolbar,
Typography,
Box,
TextField,
InputLabel,
} from "@mui/material";
import { styled } from "@mui/system";
export const AppContainer = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: 'column',
width: "100vw",
background: "rgba(39, 40, 44, 1)",
height: "100vh",
radius: "15px",
overflow: 'hidden'
}));
export const AuthenticatedContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
height: "100%",
justifyContent: "space-between"
}));
export const AuthenticatedContainerInnerLeft = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: 'column',
height: "100%",
width: "100%"
}));
export const AuthenticatedContainerInnerRight = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
flexDirection: 'column',
width: "60px",
height: "100%",
background: "rgba(0, 0, 0, 0.1)"
}));
export const AuthenticatedContainerInnerTop = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
width: "100%px",
height: "60px",
background: "rgba(0, 0, 0, 0.1)",
padding: '20px'
}));
export const TextP = styled(Typography)(({ theme }) => ({
fontSize: "13px",
fontWeight: 600,
fontFamily: "Inter",
color: "white"
}));
export const TextItalic = styled("span")(({ theme }) => ({
fontSize: "13px",
fontWeight: 600,
fontFamily: "Inter",
color: "white",
fontStyle: "italic"
}));
export const TextSpan = styled("span")(({ theme }) => ({
fontSize: "13px",
fontFamily: "Inter",
fontWeight: 800,
color: "white"
}));
export const AddressBox = styled(Box)`
display: flex;
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
justify-content: space-between;
align-items: center;
width: auto;
height: 25px;
padding: 5px 15px 5px 15px;
gap: 5px;
border-radius: 100px;
font-family: Inter;
font-size: 12px;
font-weight: 600;
line-height: 14.52px;
text-align: left;
color: var(--50-white, rgba(255, 255, 255, 0.5));
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: rgba(41, 41, 43, 1);
color: white;
svg path {
fill: white; // Fill color changes to white on hover
}
}
`
export const CustomButton = styled(Box)`
/* Authenticate */
box-sizing: border-box;
padding: 15px 20px;
gap: 10px;
border: 0.5px solid rgba(255, 255, 255, 0.5);
filter: drop-shadow(1px 4px 10.5px rgba(0, 0, 0, 0.3));
border-radius: 5px;
display: inline-flex;
justify-content: center;
align-items: center;
width: fit-content;
transition: all 0.2s;
color: black;
min-width: 160px;
cursor: pointer;
font-weight: 600;
font-family: Inter;
color: white;
text-align: center;
&:hover {
background-color: rgba(41, 41, 43, 1);
color: white;
svg path {
fill: white; // Fill color changes to white on hover
}
}
`;
interface CustomButtonProps {
bgColor?: string;
color?: string;
}
export const CustomButtonAccept = styled(Box)<CustomButtonProps>(
({ bgColor, color }) => ({
boxSizing: "border-box",
padding: "15px 20px",
gap: "10px",
border: "0.5px solid rgba(255, 255, 255, 0.5)",
filter: "drop-shadow(1px 4px 10.5px rgba(0,0,0,0.3))",
borderRadius: 5,
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
width: "fit-content",
transition: "all 0.2s",
minWidth: 160,
cursor: "pointer",
fontWeight: 600,
fontFamily: "Inter",
textAlign: "center",
opacity: 0.7,
// Use the passed-in props or fallback defaults
backgroundColor: bgColor || "transparent",
color: color || "white",
"&:hover": {
opacity: 1,
backgroundColor: bgColor
? bgColor
: "rgba(41, 41, 43, 1)", // fallback hover bg
color: color || "white",
svg: {
path: {
fill: color || "white",
},
},
},
})
);
export const CustomInput = styled(TextField)({
width: "183px", // Adjust the width as needed
borderRadius: "5px",
// backgroundColor: "rgba(30, 30, 32, 1)",
outline: "none",
input: {
fontSize: 10,
fontFamily: "Inter",
fontWeight: 400,
color: "white",
"&::placeholder": {
fontSize: 16,
color: "rgba(255, 255, 255, 0.2)",
},
outline: "none",
padding: "10px",
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
"&:hover fieldset": {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
"&.Mui-focused fieldset": {
border: '0.5px solid rgba(255, 255, 255, 0.5)',
},
},
"& .MuiInput-underline:before": {
borderBottom: "none",
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before": {
borderBottom: "none",
},
"& .MuiInput-underline:after": {
borderBottom: "none",
},
});
export const CustomLabel = styled(InputLabel)`
font-weight: 400;
font-family: Inter;
font-size: 10px;
line-height: 12px;
color: rgba(255, 255, 255, 0.5);
`

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,6 +1,10 @@
import React from "react";
import { useTheme } from '@mui/material';
export const AppsIcon = ({ height = 31, width = 31, color }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
export const AppsIcon = ({ color, height = 31, width = 31 }) => {
return (
<svg
width={width}
@@ -11,39 +15,39 @@ export const AppsIcon = ({ color, height = 31, width = 31 }) => {
>
<path
d="M3.76596 7.53192C5.84584 7.53192 7.53192 5.84584 7.53192 3.76596C7.53192 1.68608 5.84584 0 3.76596 0C1.68608 0 0 1.68608 0 3.76596C0 5.84584 1.68608 7.53192 3.76596 7.53192Z"
fill={color}
fill={setColor}
/>
<path
d="M15 7.53192C17.0799 7.53192 18.766 5.84584 18.766 3.76596C18.766 1.68608 17.0799 0 15 0C12.9201 0 11.2341 1.68608 11.2341 3.76596C11.2341 5.84584 12.9201 7.53192 15 7.53192Z"
fill={color}
fill={setColor}
/>
<path
d="M26.234 7.53192C28.3139 7.53192 30 5.84584 30 3.76596C30 1.68608 28.3139 0 26.234 0C24.1542 0 22.4681 1.68608 22.4681 3.76596C22.4681 5.84584 24.1542 7.53192 26.234 7.53192Z"
fill={color}
fill={setColor}
/>
<path
d="M3.76596 30.0001C5.84584 30.0001 7.53192 28.314 7.53192 26.2341C7.53192 24.1542 5.84584 22.4681 3.76596 22.4681C1.68608 22.4681 0 24.1542 0 26.2341C0 28.314 1.68608 30.0001 3.76596 30.0001Z"
fill={color}
fill={setColor}
/>
<path
d="M15 30.0002C17.0799 30.0002 18.766 28.3141 18.766 26.2342C18.766 24.1543 17.0799 22.4683 15 22.4683C12.9201 22.4683 11.2341 24.1543 11.2341 26.2342C11.2341 28.3141 12.9201 30.0002 15 30.0002Z"
fill={color}
fill={setColor}
/>
<path
d="M26.234 30.0002C28.3139 30.0002 30 28.3141 30 26.2342C30 24.1543 28.3139 22.4683 26.234 22.4683C24.1542 22.4683 22.4681 24.1543 22.4681 26.2342C22.4681 28.3141 24.1542 30.0002 26.234 30.0002Z"
fill={color}
fill={setColor}
/>
<path
d="M3.76596 18.766C5.84584 18.766 7.53192 17.08 7.53192 15.0001C7.53192 12.9202 5.84584 11.2341 3.76596 11.2341C1.68608 11.2341 0 12.9202 0 15.0001C0 17.08 1.68608 18.766 3.76596 18.766Z"
fill={color}
fill={setColor}
/>
<path
d="M15 18.766C17.0799 18.766 18.766 17.08 18.766 15.0001C18.766 12.9202 17.0799 11.2341 15 11.2341C12.9201 11.2341 11.2341 12.9202 11.2341 15.0001C11.2341 17.08 12.9201 18.766 15 18.766Z"
fill={color}
fill={setColor}
/>
<path
d="M26.234 18.766C28.3139 18.766 30 17.08 30 15.0001C30 12.9202 28.3139 11.2341 26.234 11.2341C24.1542 11.2341 22.4681 12.9202 22.4681 15.0001C22.4681 17.08 24.1542 18.766 26.234 18.766Z"
fill={color}
fill={setColor}
/>
</svg>
);

View File

@@ -1,12 +1,18 @@
import React from 'react';
export const ChatIcon= ({ color = 'white', height = 15, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 0C3.35915 0 0 3.35915 0 7.5V13.8169C0 14.4718 0.528169 15 1.1831 15H7.5C11.6408 15 15 11.6408 15 7.5C15 3.35915 11.6408 0 7.5 0ZM11.0915 10.669H3.90845C3.67606 10.669 3.48592 10.4789 3.48592 10.2465C3.48592 10.0141 3.67606 9.82394 3.90845 9.82394H11.0915C11.3239 9.82394 11.5141 10.0141 11.5141 10.2465C11.5141 10.4789 11.3239 10.669 11.0915 10.669ZM11.0915 8.34507H3.90845C3.67606 8.34507 3.48592 8.15493 3.48592 7.92254C3.48592 7.69014 3.67606 7.5 3.90845 7.5H11.0915C11.3239 7.5 11.5141 7.69014 11.5141 7.92254C11.5141 8.15493 11.3239 8.34507 11.0915 8.34507ZM11.0915 6.02113H3.90845C3.67606 6.02113 3.48592 5.83099 3.48592 5.59859C3.48592 5.3662 3.67606 5.17606 3.90845 5.17606H11.0915C11.3239 5.17606 11.5141 5.3662 11.5141 5.59859C11.5141 5.83099 11.3239 6.02113 11.0915 6.02113Z" fill={color}/>
</svg>
);
};
export const ChatIcon = ({ color = 'white', height = 15, width = 15 }) => {
return (
<svg
width={width}
height={height}
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 0C3.35915 0 0 3.35915 0 7.5V13.8169C0 14.4718 0.528169 15 1.1831 15H7.5C11.6408 15 15 11.6408 15 7.5C15 3.35915 11.6408 0 7.5 0ZM11.0915 10.669H3.90845C3.67606 10.669 3.48592 10.4789 3.48592 10.2465C3.48592 10.0141 3.67606 9.82394 3.90845 9.82394H11.0915C11.3239 9.82394 11.5141 10.0141 11.5141 10.2465C11.5141 10.4789 11.3239 10.669 11.0915 10.669ZM11.0915 8.34507H3.90845C3.67606 8.34507 3.48592 8.15493 3.48592 7.92254C3.48592 7.69014 3.67606 7.5 3.90845 7.5H11.0915C11.3239 7.5 11.5141 7.69014 11.5141 7.92254C11.5141 8.15493 11.3239 8.34507 11.0915 8.34507ZM11.0915 6.02113H3.90845C3.67606 6.02113 3.48592 5.83099 3.48592 5.59859C3.48592 5.3662 3.67606 5.17606 3.90845 5.17606H11.0915C11.3239 5.17606 11.5141 5.3662 11.5141 5.59859C11.5141 5.83099 11.3239 6.02113 11.0915 6.02113Z"
fill={color}
/>
</svg>
);
};

View File

@@ -0,0 +1,33 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const ComposeIcon: React.FC<SVGProps> = ({
color,
height = 20,
width = 20,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width={width}
height={height}
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M50.3 3c1.5 0 3.9 0.6 5.5 1.4 1.5 0.7 3.3 2.5 4 4 0.6 1.4 1.2 3.7 1.2 5.1 0 1.4-0.6 3.7-1.4 5.3-0.8 1.5-9.9 11.1-39.1 40l-6.7 1.6c-3.8 0.9-7.9 1.6-9.3 1.6-1.8 0-2.5-0.5-2.5-2 0-1.1 0.7-5.3 3.2-16.5l18.1-18.4c10-10 19.6-19.2 21.2-20.2 1.7-1 4.2-1.9 5.8-1.9zm-8.4 11.3c0 0.7 1.5 2.7 3.3 4.4 1.8 1.8 3.9 3.3 4.6 3.3 0.6 0 2.3-1.4 3.7-3 1.4-1.7 2.5-4 2.5-5.3 0.1-1.2-0.7-3-1.7-3.9-1-1-2.8-1.8-4-1.8-1.2 0-3.5 1.2-5.3 2.6-1.7 1.4-3.1 3.1-3.1 3.7z"
fill={setColor}
opacity={setOpacity}
fillRule="evenodd"
/>
</svg>
);
};

View File

@@ -0,0 +1,25 @@
import { useTheme } from '@mui/material';
import React from 'react';
export const CopyIcon = ({ color, height = 11, width = 10 }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
width={width}
height={height}
viewBox="0 0 10 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.92857 0.5H8.57143C9.36071 0.5 10 1.13929 10 1.92857V6.57143C10 7.36071 9.36071 8 8.57143 8H8.21429V4.42857C8.21429 3.24643 7.25357 2.28571 6.07143 2.28571H2.5V1.92857C2.5 1.13929 3.13929 0.5 3.92857 0.5ZM1.42857 3H6.07143C6.86041 3 7.5 3.63959 7.5 4.42857V9.07143C7.5 9.86041 6.86041 10.5 6.07143 10.5H1.42857C0.639593 10.5 0 9.86041 0 9.07143V4.42857C0 3.63959 0.639593 3 1.42857 3Z"
fill={setColor}
fill-opacity="0.5"
/>
</svg>
);
};

View File

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

View File

@@ -1,13 +1,20 @@
import React from 'react';
import { useTheme } from '@mui/material';
export const ExitIcon= ({ color = 'white', height, width }) => {
return (
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z" fill={color}/>
</svg>
export const ExitIcon = () => {
const theme = useTheme();
);
};
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 0L0 2L4 6L0 10L2 12L6 8L10 12L12 10L8 6L12 2L10 0L6 4L2 0Z"
fill={theme.palette.text.primary}
/>
</svg>
);
};

View File

@@ -1,12 +1,21 @@
import React from 'react';
import { useTheme } from '@mui/material';
export const HomeIcon= ({ color, height = 20, width = 23 }) => {
return (
<svg width={width} height={height} viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.936 11.9842C22.8122 12.2972 22.5127 12.502 22.1801 12.5008H19.7155V19.1668C19.714 19.6263 19.3471 19.9985 18.8939 20H14.7862V14.1673C14.7862 12.3265 13.3149 10.8343 11.5 10.8343C9.68509 10.8343 8.21381 12.3265 8.21381 14.1673V20H4.10607C3.65294 19.9985 3.28596 19.6263 3.28452 19.1668V12.5008H0.819874C0.487346 12.502 0.187777 12.2972 0.0640491 11.9842C-0.0642812 11.6739 0.00375033 11.3157 0.236574 11.076L10.9167 0.243778C11.2395 -0.0812593 11.7605 -0.0812593 12.0833 0.243778L22.7634 11.076C22.9963 11.3157 23.0643 11.6739 22.936 11.9842Z" fill={color}/>
</svg>
export const HomeIcon = ({ height = 20, width = 23, color }) => {
const theme = useTheme();
);
};
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
width={width}
height={height}
viewBox="0 0 23 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22.936 11.9842C22.8122 12.2972 22.5127 12.502 22.1801 12.5008H19.7155V19.1668C19.714 19.6263 19.3471 19.9985 18.8939 20H14.7862V14.1673C14.7862 12.3265 13.3149 10.8343 11.5 10.8343C9.68509 10.8343 8.21381 12.3265 8.21381 14.1673V20H4.10607C3.65294 19.9985 3.28596 19.6263 3.28452 19.1668V12.5008H0.819874C0.487346 12.502 0.187777 12.2972 0.0640491 11.9842C-0.0642812 11.6739 0.00375033 11.3157 0.236574 11.076L10.9167 0.243778C11.2395 -0.0812593 11.7605 -0.0812593 12.0833 0.243778L22.7634 11.076C22.9963 11.3157 23.0643 11.6739 22.936 11.9842Z"
fill={setColor}
/>
</svg>
);
};

View File

@@ -1,12 +0,0 @@
import React from 'react';
export const LogoutIcon= ({ color, height = 20, width = 18}) => {
return (
<svg width={width} height={height} viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4351 0H1.63891C0.733765 0 0 0.727797 0 1.62558V18.3744C0 19.2722 0.733765 20 1.63891 20H10.4351C11.3403 20 12.0741 19.2722 12.0741 18.3744V12.6013H7.38321C6.54312 12.6013 5.85964 11.9039 5.85964 11.0467V8.87329C5.85964 8.01613 6.54312 7.31875 7.38323 7.31875H12.0741V1.62558C12.0741 0.727797 11.3403 0 10.4351 0ZM6.83334 11.0467C6.83334 11.3719 7.07952 11.6354 7.38321 11.6354H13.1856C13.2548 11.6354 13.3109 11.6955 13.3109 11.7696V12.8632C13.3109 13.3492 13.8299 13.6259 14.1922 13.3329L17.7816 10.4298C18.0728 10.1942 18.0728 9.72579 17.7816 9.49024L14.1922 6.58709C13.8299 6.29409 13.3109 6.57077 13.3109 7.05684V8.1504C13.3109 8.2245 13.2548 8.28454 13.1856 8.28454H7.38322C7.07952 8.28454 6.83334 8.54813 6.83334 8.87329V11.0467Z" fill={color} />
</svg>
);
};

View File

@@ -1,13 +1,30 @@
import React from 'react';
import { useTheme } from "@mui/material";
export const MessagingIcon= ({ color, height = 31, width = 31 }) => {
return (
<svg width={width} height={height} viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.3937 4.49877C23.6712 1.56681 3.1922 8.74911 3.20912 11.3714C3.22829 14.345 11.2067 15.2597 13.4181 15.8802C14.748 16.2532 15.1041 16.6357 15.4107 18.0302C16.7995 24.3457 17.4967 27.487 19.0859 27.5571C21.6189 27.6691 29.0507 7.36011 26.3937 4.49877Z" stroke={color} stroke-width="2"/>
<path d="M14.4591 16.3076L18.8341 11.9326" stroke={color} stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
export const MessagingIcon = ({ color, height = 31, width = 31 }) => {
const theme = useTheme();
);
};
const setColor = color ? color : theme.palette.text.primary
return (
<svg
width={width}
height={height}
viewBox="0 0 31 31"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M26.3937 4.49877C23.6712 1.56681 3.1922 8.74911 3.20912 11.3714C3.22829 14.345 11.2067 15.2597 13.4181 15.8802C14.748 16.2532 15.1041 16.6357 15.4107 18.0302C16.7995 24.3457 17.4967 27.487 19.0859 27.5571C21.6189 27.6691 29.0507 7.36011 26.3937 4.49877Z"
stroke={setColor}
strokeWidth="2"
/>
<path
d="M14.4591 16.3076L18.8341 11.9326"
stroke={setColor}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};

View File

@@ -1,14 +0,0 @@
import React from 'react';
export const MessagingIcon2= ({ color = '#8F8F91', height = 24, width =24 }) => {
return (
<svg width={width} height={height} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.6636 0.00168233C22.6127 -0.000756257 22.5614 -0.000627677 22.5099 0.00261984C22.3724 0.0112798 22.2331 0.0405753 22.0969 0.093558L1.02096 8.28971C0.362343 8.54585 -0.00366118 9.18408 2.76147e-05 9.79253C0.00371641 10.401 0.377567 11.0341 1.03925 11.2822L9.02065 14.2752C9.34631 14.3974 9.60258 14.6536 9.72471 14.9793L12.7177 22.9607C12.9658 23.6224 13.5989 23.9963 14.2074 24C14.8158 24.0037 15.454 23.6376 15.7102 22.979L23.9063 1.90295C24.1182 1.35797 23.9526 0.768987 23.5917 0.408091C23.3549 0.171254 23.02 0.0187526 22.6636 0.00168233ZM18.4022 4.99812C18.5613 4.99815 18.7139 5.06138 18.8264 5.17391C18.9389 5.28643 19.0021 5.43902 19.0021 5.59813C19.0021 5.75724 18.9389 5.90983 18.8264 6.02235L13.2239 11.6244C13.1114 11.7369 12.9588 11.8001 12.7997 11.8001C12.6406 11.8001 12.488 11.7369 12.3755 11.6244C12.263 11.5119 12.1998 11.3593 12.1998 11.2002C12.1998 11.0411 12.263 10.8885 12.3755 10.776L17.9775 5.17391C18.0333 5.11813 18.0995 5.0739 18.1724 5.04374C18.2452 5.01357 18.3233 4.99807 18.4022 4.99812Z" fill={color}/>
</svg>
);
};

View File

@@ -0,0 +1,24 @@
import { useTheme } from '@mui/material';
import React from 'react';
export const MessagingIconFilled = ({ color, height = 31, width = 31 }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
width={width}
height={height}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M22.6636 0.00168233C22.6127 -0.000756257 22.5614 -0.000627677 22.5099 0.00261984C22.3724 0.0112798 22.2331 0.0405753 22.0969 0.093558L1.02096 8.28971C0.362343 8.54585 -0.00366118 9.18408 2.76147e-05 9.79253C0.00371641 10.401 0.377567 11.0341 1.03925 11.2822L9.02065 14.2752C9.34631 14.3974 9.60258 14.6536 9.72471 14.9793L12.7177 22.9607C12.9658 23.6224 13.5989 23.9963 14.2074 24C14.8158 24.0037 15.454 23.6376 15.7102 22.979L23.9063 1.90295C24.1182 1.35797 23.9526 0.768987 23.5917 0.408091C23.3549 0.171254 23.02 0.0187526 22.6636 0.00168233ZM18.4022 4.99812C18.5613 4.99815 18.7139 5.06138 18.8264 5.17391C18.9389 5.28643 19.0021 5.43902 19.0021 5.59813C19.0021 5.75724 18.9389 5.90983 18.8264 6.02235L13.2239 11.6244C13.1114 11.7369 12.9588 11.8001 12.7997 11.8001C12.6406 11.8001 12.488 11.7369 12.3755 11.6244C12.263 11.5119 12.1998 11.3593 12.1998 11.2002C12.1998 11.0411 12.263 10.8885 12.3755 10.776L17.9775 5.17391C18.0333 5.11813 18.0995 5.0739 18.1724 5.04374C18.2452 5.01357 18.3233 4.99807 18.4022 4.99812Z"
fill={setColor}
/>
</svg>
);
};

View File

@@ -0,0 +1,32 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const NavAdd: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="20"
cy="19.9999"
r="18"
fill={theme.palette.background.paper}
/>
<path
d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z"
fill={setColor}
fillOpacity={setOpacity}
/>
</svg>
);
};

View File

@@ -0,0 +1,30 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const NavBack: React.FC<SVGProps> = ({
color,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M28.3334 15.5833H11.0925L19.0117 7.6641L17 5.6666L5.66669 16.9999L17 28.3333L18.9975 26.3358L11.0925 18.4166H28.3334V15.5833Z"
fill={setColor}
opacity={setOpacity}
/>
</svg>
);
};

View File

@@ -0,0 +1,46 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const NavCloseTab: React.FC<SVGProps> = ({
color,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="17"
height="17"
viewBox="0 0 17 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="8.5" cy="8.5" r="8.5" fill={theme.palette.text.primary} />
<circle
cx="8.5"
cy="8.50003"
r="6.61111"
fill={theme.palette.background.paper}
/>
<path
d="M5.66675 5.66669L11.3334 11.3334"
stroke={theme.palette.text.primary}
stroke-width="2"
fill={setColor}
fillOpacity={setOpacity}
/>
<path
d="M11.3333 5.66675L5.66658 11.3334"
stroke={theme.palette.text.primary}
stroke-width="2"
fill={setColor}
fillOpacity={setOpacity}
/>
</svg>
);
};

View File

@@ -0,0 +1,30 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const NavMoreMenu: React.FC<SVGProps> = ({
color,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z"
fill={setColor}
fillOpacity={setOpacity}
/>
</svg>
);
};

View File

@@ -3,7 +3,7 @@ import React from 'react';
export const NotificationIcon2= ({ color = 'white', height = 15, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.50253 0.80505C8.50253 0.360285 8.14231 0 7.69818 0C7.25348 0 6.89325 0.36027 6.89325 0.80505V1.6142C6.89325 2.05896 7.25346 2.41866 7.69818 2.41866C8.14229 2.41866 8.50253 2.05839 8.50253 1.61361V0.80505ZM7.06691 3.12343C7.45178 2.90163 7.944 3.03366 8.16577 3.41857L12.3049 10.5872C12.5231 10.9715 12.3905 11.4603 12.0074 11.6815C11.6243 11.9027 11.1344 11.773 10.9109 11.3916L10.8517 11.2884L7.53454 12.0588C7.73401 12.7922 7.56388 13.5767 7.07751 14.1606C6.59173 14.7444 5.85192 15.0548 5.09454 14.992C4.33771 14.9286 3.65892 14.5009 3.27588 13.8443L3.27353 13.8414L2.86989 13.1431L1.30986 13.5046L1.31045 13.504C1.08047 13.5574 0.842856 13.4547 0.724941 13.2499L0.0713702 12.118C-0.0459665 11.9138 -0.0166327 11.6562 0.144705 11.4837L6.83346 4.32684L6.77245 4.22298C6.66567 4.03814 6.63633 3.8187 6.69207 3.61216C6.74722 3.40561 6.8821 3.23022 7.06691 3.12343ZM4.50841 12.7617L5.96397 12.4237C6.09539 12.7746 5.9364 13.1683 5.59729 13.3279C5.25819 13.4875 4.85338 13.3602 4.66623 13.0351L4.50841 12.7617ZM15 7.30235C15 7.51593 14.9155 7.72072 14.7647 7.87152C14.614 8.02232 14.4092 8.1074 14.1957 8.1074H13.3866C12.9419 8.1074 12.5823 7.74713 12.5823 7.30235C12.5823 6.85817 12.9425 6.49788 13.3872 6.49788H14.1951C14.6398 6.49788 15 6.85815 15 7.30235ZM2.01088 8.10681C2.45558 8.10681 2.81582 7.74713 2.81582 7.30235C2.81582 6.85817 2.4556 6.49788 2.01088 6.49788H1.20185C0.757149 6.49788 0.397502 6.85815 0.397502 7.30235C0.397502 7.74711 0.75772 8.1074 1.20244 8.1074H2.01147L2.01088 8.10681ZM4.35176 3.95659C4.03789 4.2705 3.52864 4.27109 3.21477 3.95717L2.53481 3.27712C2.37465 3.12808 2.28253 2.92036 2.27843 2.70209C2.27491 2.48381 2.35998 2.27374 2.51428 2.11884C2.66857 1.96453 2.8792 1.87943 3.09744 1.88355C3.31568 1.88766 3.52278 1.97978 3.6718 2.13937L4.35295 2.81826C4.50431 2.96906 4.58879 3.17443 4.58879 3.38802C4.58879 3.60161 4.50431 3.80639 4.35295 3.95719L4.35176 3.95659ZM12.861 3.27653L12.8616 3.27712C13.1567 2.95967 13.1479 2.46562 12.8416 2.15932C12.5354 1.85302 12.0414 1.84422 11.724 2.13937L11.0352 2.82589C10.7214 3.1398 10.7214 3.6503 11.0352 3.9648C11.3497 4.27872 11.8595 4.27872 12.1734 3.9648L12.861 3.27829V3.27653Z" fill={color}/>
<path fillRule="evenodd" clipRule="evenodd" d="M8.50253 0.80505C8.50253 0.360285 8.14231 0 7.69818 0C7.25348 0 6.89325 0.36027 6.89325 0.80505V1.6142C6.89325 2.05896 7.25346 2.41866 7.69818 2.41866C8.14229 2.41866 8.50253 2.05839 8.50253 1.61361V0.80505ZM7.06691 3.12343C7.45178 2.90163 7.944 3.03366 8.16577 3.41857L12.3049 10.5872C12.5231 10.9715 12.3905 11.4603 12.0074 11.6815C11.6243 11.9027 11.1344 11.773 10.9109 11.3916L10.8517 11.2884L7.53454 12.0588C7.73401 12.7922 7.56388 13.5767 7.07751 14.1606C6.59173 14.7444 5.85192 15.0548 5.09454 14.992C4.33771 14.9286 3.65892 14.5009 3.27588 13.8443L3.27353 13.8414L2.86989 13.1431L1.30986 13.5046L1.31045 13.504C1.08047 13.5574 0.842856 13.4547 0.724941 13.2499L0.0713702 12.118C-0.0459665 11.9138 -0.0166327 11.6562 0.144705 11.4837L6.83346 4.32684L6.77245 4.22298C6.66567 4.03814 6.63633 3.8187 6.69207 3.61216C6.74722 3.40561 6.8821 3.23022 7.06691 3.12343ZM4.50841 12.7617L5.96397 12.4237C6.09539 12.7746 5.9364 13.1683 5.59729 13.3279C5.25819 13.4875 4.85338 13.3602 4.66623 13.0351L4.50841 12.7617ZM15 7.30235C15 7.51593 14.9155 7.72072 14.7647 7.87152C14.614 8.02232 14.4092 8.1074 14.1957 8.1074H13.3866C12.9419 8.1074 12.5823 7.74713 12.5823 7.30235C12.5823 6.85817 12.9425 6.49788 13.3872 6.49788H14.1951C14.6398 6.49788 15 6.85815 15 7.30235ZM2.01088 8.10681C2.45558 8.10681 2.81582 7.74713 2.81582 7.30235C2.81582 6.85817 2.4556 6.49788 2.01088 6.49788H1.20185C0.757149 6.49788 0.397502 6.85815 0.397502 7.30235C0.397502 7.74711 0.75772 8.1074 1.20244 8.1074H2.01147L2.01088 8.10681ZM4.35176 3.95659C4.03789 4.2705 3.52864 4.27109 3.21477 3.95717L2.53481 3.27712C2.37465 3.12808 2.28253 2.92036 2.27843 2.70209C2.27491 2.48381 2.35998 2.27374 2.51428 2.11884C2.66857 1.96453 2.8792 1.87943 3.09744 1.88355C3.31568 1.88766 3.52278 1.97978 3.6718 2.13937L4.35295 2.81826C4.50431 2.96906 4.58879 3.17443 4.58879 3.38802C4.58879 3.60161 4.50431 3.80639 4.35295 3.95719L4.35176 3.95659ZM12.861 3.27653L12.8616 3.27712C13.1567 2.95967 13.1479 2.46562 12.8416 2.15932C12.5354 1.85302 12.0414 1.84422 11.724 2.13937L11.0352 2.82589C10.7214 3.1398 10.7214 3.6503 11.0352 3.9648C11.3497 4.27872 11.8595 4.27872 12.1734 3.9648L12.861 3.27829V3.27653Z" fill={color}/>
</svg>
);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const QappLibraryText: React.FC<SVGProps> = ({
color,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="301"
height="26"
viewBox="0 0 301 26"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.5 15.3636H14.4091L16.1818 17.5909L19.1818 21L23.0909 25.7273H17.5L14.7273 22.5L12.8636 19.8182L9.5 15.3636ZM23.0455 12.3636C23.0455 14.9545 22.5417 17.1402 21.5341 18.9205C20.5265 20.6932 19.1667 22.0379 17.4545 22.9545C15.7424 23.8636 13.8333 24.3182 11.7273 24.3182C9.60606 24.3182 7.68939 23.8598 5.97727 22.9432C4.27273 22.0189 2.91667 20.6705 1.90909 18.8977C0.909091 17.1174 0.409091 14.9394 0.409091 12.3636C0.409091 9.77273 0.909091 7.59091 1.90909 5.81818C2.91667 4.03788 4.27273 2.69318 5.97727 1.78409C7.68939 0.867423 9.60606 0.40909 11.7273 0.40909C13.8333 0.40909 15.7424 0.867423 17.4545 1.78409C19.1667 2.69318 20.5265 4.03788 21.5341 5.81818C22.5417 7.59091 23.0455 9.77273 23.0455 12.3636ZM16.5455 12.3636C16.5455 10.9697 16.3598 9.79545 15.9886 8.84091C15.625 7.87879 15.0833 7.15151 14.3636 6.65909C13.6515 6.15909 12.7727 5.90909 11.7273 5.90909C10.6818 5.90909 9.79924 6.15909 9.07955 6.65909C8.36742 7.15151 7.82576 7.87879 7.45455 8.84091C7.09091 9.79545 6.90909 10.9697 6.90909 12.3636C6.90909 13.7576 7.09091 14.9356 7.45455 15.8977C7.82576 16.8523 8.36742 17.5795 9.07955 18.0795C9.79924 18.572 10.6818 18.8182 11.7273 18.8182C12.7727 18.8182 13.6515 18.572 14.3636 18.0795C15.0833 17.5795 15.625 16.8523 15.9886 15.8977C16.3598 14.9356 16.5455 13.7576 16.5455 12.3636ZM39.5455 10V14.7273H28.6364V10H39.5455ZM51.233 24H44.4148L52.0966 0.727272H60.733L68.4148 24H61.5966L56.5057 7.13636H56.3239L51.233 24ZM49.9602 14.8182H62.7784V19.5455H49.9602V14.8182ZM72.6562 24V0.727272H82.7017C84.429 0.727272 85.9403 1.06818 87.2358 1.75C88.5313 2.43182 89.5388 3.39015 90.2585 4.625C90.9782 5.85985 91.3381 7.30303 91.3381 8.95455C91.3381 10.6212 90.9669 12.0644 90.2244 13.2841C89.4896 14.5038 88.4555 15.4432 87.1222 16.1023C85.7964 16.7614 84.2472 17.0909 82.4744 17.0909H76.4744V12.1818H81.2017C81.9441 12.1818 82.5767 12.053 83.0994 11.7955C83.6297 11.5303 84.035 11.1553 84.3153 10.6705C84.6032 10.1856 84.7472 9.61364 84.7472 8.95455C84.7472 8.28788 84.6032 7.7197 84.3153 7.25C84.035 6.77273 83.6297 6.40909 83.0994 6.15909C82.5767 5.90151 81.9441 5.77273 81.2017 5.77273H78.9744V24H72.6562ZM95.6562 24V0.727272H105.702C107.429 0.727272 108.94 1.06818 110.236 1.75C111.531 2.43182 112.539 3.39015 113.259 4.625C113.978 5.85985 114.338 7.30303 114.338 8.95455C114.338 10.6212 113.967 12.0644 113.224 13.2841C112.49 14.5038 111.455 15.4432 110.122 16.1023C108.796 16.7614 107.247 17.0909 105.474 17.0909H99.4744V12.1818H104.202C104.944 12.1818 105.577 12.053 106.099 11.7955C106.63 11.5303 107.035 11.1553 107.315 10.6705C107.603 10.1856 107.747 9.61364 107.747 8.95455C107.747 8.28788 107.603 7.7197 107.315 7.25C107.035 6.77273 106.63 6.40909 106.099 6.15909C105.577 5.90151 104.944 5.77273 104.202 5.77273H101.974V24H95.6562ZM131.202 8C131.141 7.24242 130.857 6.65151 130.349 6.22727C129.849 5.80303 129.088 5.59091 128.065 5.59091C127.414 5.59091 126.88 5.67045 126.463 5.82954C126.054 5.98106 125.751 6.18939 125.554 6.45454C125.357 6.7197 125.255 7.02273 125.247 7.36364C125.232 7.64394 125.281 7.89773 125.395 8.125C125.516 8.3447 125.705 8.54545 125.963 8.72727C126.221 8.90151 126.55 9.06061 126.952 9.20455C127.353 9.34848 127.83 9.47727 128.384 9.59091L130.293 10C131.58 10.2727 132.683 10.6326 133.599 11.0795C134.516 11.5265 135.266 12.053 135.849 12.6591C136.433 13.2576 136.861 13.9318 137.134 14.6818C137.414 15.4318 137.558 16.25 137.565 17.1364C137.558 18.6667 137.175 19.9621 136.418 21.0227C135.66 22.0833 134.577 22.8902 133.168 23.4432C131.766 23.9962 130.08 24.2727 128.111 24.2727C126.088 24.2727 124.323 23.9735 122.815 23.375C121.315 22.7765 120.149 21.8561 119.315 20.6136C118.49 19.3636 118.073 17.7652 118.065 15.8182H124.065C124.103 16.5303 124.281 17.1288 124.599 17.6136C124.918 18.0985 125.365 18.4659 125.94 18.7159C126.524 18.9659 127.217 19.0909 128.02 19.0909C128.694 19.0909 129.259 19.0076 129.713 18.8409C130.168 18.6742 130.512 18.4432 130.747 18.1477C130.982 17.8523 131.103 17.5152 131.111 17.1364C131.103 16.7803 130.986 16.4697 130.759 16.2045C130.539 15.9318 130.175 15.6894 129.668 15.4773C129.16 15.2576 128.474 15.053 127.611 14.8636L125.293 14.3636C123.232 13.9167 121.607 13.1705 120.418 12.125C119.236 11.072 118.649 9.63636 118.656 7.81818C118.649 6.34091 119.043 5.04924 119.838 3.94318C120.641 2.82954 121.751 1.96212 123.168 1.34091C124.592 0.719696 126.224 0.40909 128.065 0.40909C129.944 0.40909 131.569 0.723484 132.94 1.35227C134.312 1.98106 135.368 2.86742 136.111 4.01136C136.861 5.14773 137.24 6.47727 137.247 8H131.202Z"
fill={setColor}
opacity={setOpacity}
/>
<path
d="M150.344 24V0.727272H156.662V18.9091H166.071V24H150.344ZM176.943 0.727272V24H170.625V0.727272H176.943ZM181.938 24V0.727272H192.028C193.801 0.727272 195.29 0.965909 196.494 1.44318C197.706 1.92045 198.619 2.5947 199.233 3.46591C199.854 4.33712 200.165 5.36364 200.165 6.54545C200.165 7.40151 199.975 8.18182 199.597 8.88636C199.225 9.59091 198.703 10.1818 198.028 10.6591C197.354 11.1288 196.566 11.4545 195.665 11.6364V11.8636C196.665 11.9015 197.574 12.1553 198.392 12.625C199.21 13.0871 199.862 13.7273 200.347 14.5455C200.831 15.3561 201.074 16.3106 201.074 17.4091C201.074 18.6818 200.741 19.8144 200.074 20.8068C199.415 21.7992 198.475 22.5795 197.256 23.1477C196.036 23.7159 194.581 24 192.892 24H181.938ZM188.256 18.9545H191.21C192.271 18.9545 193.066 18.7576 193.597 18.3636C194.127 17.9621 194.392 17.3712 194.392 16.5909C194.392 16.0455 194.267 15.5833 194.017 15.2045C193.767 14.8258 193.411 14.5379 192.949 14.3409C192.494 14.1439 191.945 14.0455 191.301 14.0455H188.256V18.9545ZM188.256 10.1364H190.847C191.4 10.1364 191.888 10.0492 192.312 9.875C192.737 9.70076 193.066 9.45076 193.301 9.125C193.544 8.79167 193.665 8.38636 193.665 7.90909C193.665 7.18939 193.407 6.64015 192.892 6.26136C192.377 5.875 191.725 5.68182 190.938 5.68182H188.256V10.1364ZM205.312 24V0.727272H215.358C217.085 0.727272 218.597 1.04167 219.892 1.67045C221.188 2.29924 222.195 3.20455 222.915 4.38636C223.634 5.56818 223.994 6.98485 223.994 8.63636C223.994 10.303 223.623 11.7083 222.881 12.8523C222.146 13.9962 221.112 14.8598 219.778 15.4432C218.453 16.0265 216.903 16.3182 215.131 16.3182H209.131V11.4091H213.858C214.6 11.4091 215.233 11.3182 215.756 11.1364C216.286 10.947 216.691 10.6477 216.972 10.2386C217.259 9.82955 217.403 9.29545 217.403 8.63636C217.403 7.9697 217.259 7.42803 216.972 7.01136C216.691 6.58712 216.286 6.27651 215.756 6.07954C215.233 5.875 214.6 5.77273 213.858 5.77273H211.631V24H205.312ZM218.949 13.3182L224.767 24H217.903L212.222 13.3182H218.949ZM234.733 24H227.915L235.597 0.727272H244.233L251.915 24H245.097L240.006 7.13636H239.824L234.733 24ZM233.46 14.8182H246.278V19.5455H233.46V14.8182ZM256.156 24V0.727272H266.202C267.929 0.727272 269.44 1.04167 270.736 1.67045C272.031 2.29924 273.039 3.20455 273.759 4.38636C274.478 5.56818 274.838 6.98485 274.838 8.63636C274.838 10.303 274.467 11.7083 273.724 12.8523C272.99 13.9962 271.955 14.8598 270.622 15.4432C269.296 16.0265 267.747 16.3182 265.974 16.3182H259.974V11.4091H264.702C265.444 11.4091 266.077 11.3182 266.599 11.1364C267.13 10.947 267.535 10.6477 267.815 10.2386C268.103 9.82955 268.247 9.29545 268.247 8.63636C268.247 7.9697 268.103 7.42803 267.815 7.01136C267.535 6.58712 267.13 6.27651 266.599 6.07954C266.077 5.875 265.444 5.77273 264.702 5.77273H262.474V24H256.156ZM269.793 13.3182L275.611 24H268.747L263.065 13.3182H269.793ZM277.483 0.727272H284.528L289.074 10.1818H289.256L293.801 0.727272H300.847L292.301 16.6818V24H286.028V16.6818L277.483 0.727272Z"
fill="#0091E1"
/>
</svg>
);
};

View File

@@ -0,0 +1,27 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Return: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
width="20"
height="16"
{...children}
viewBox="0 0 20 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.645 5.81803H15C15.9471 5.81803 16.8557 6.20131 17.5257 6.88278C18.195 7.56497 18.5714 8.49007 18.5714 9.45445V10.909C18.5714 11.8734 18.195 12.7985 17.5257 13.4807C16.8557 14.1622 15.9471 14.5454 15 14.5454C12.0164 14.5454 8.57143 14.5454 8.57143 14.5454C8.17714 14.5454 7.85714 14.8713 7.85714 15.2727C7.85714 15.6742 8.17714 16 8.57143 16H15C16.3264 16 17.5979 15.464 18.5357 14.5091C19.4736 13.5541 20 12.2596 20 10.909C20 10.4268 20 9.93664 20 9.45445C20 8.10461 19.4736 6.80932 18.5357 5.8544C17.5979 4.9002 16.3264 4.36347 15 4.36347H2.645L6.17929 1.27906C6.47857 1.01797 6.51286 0.55832 6.25643 0.253588C6 -0.0511433 5.54857 -0.0860541 5.24929 0.175041L0.249285 4.53874C0.0914279 4.67692 0 4.87838 0 5.09075C0 5.30312 0.0914279 5.50458 0.249285 5.64276L5.24929 10.0065C5.54857 10.2676 6 10.2326 6.25643 9.92791C6.51286 9.62318 6.47857 9.16353 6.17929 8.90244L2.645 5.81803Z"
fill={setColor}
fillOpacity={opacity}
/>
</svg>
);
};

View File

@@ -1,14 +1,20 @@
import React from 'react';
import { useTheme } from '@mui/material';
export const ReturnIcon= ({ color = 'white', height, width }) => {
return (
<svg width="20" height="13" viewBox="0 0 20 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z" fill={color}/>
</svg>
export const ReturnIcon = () => {
const theme = useTheme();
);
};
return (
<svg
fill="none"
height="13"
viewBox="0 0 20 13"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.0937 3.73451H3.6243L5.84808 1.67047C6.04153 1.48456 6.14857 1.23558 6.14615 0.97713C6.14373 0.718684 6.03205 0.471459 5.83515 0.288703C5.63825 0.105948 5.37189 0.00228309 5.09344 3.72619e-05C4.815 -0.00220856 4.54674 0.0971438 4.34645 0.276696L0.310933 4.02233C0.111843 4.20718 0 4.45785 0 4.71922C0 4.98059 0.111843 5.23126 0.310933 5.41611L4.34645 9.16175C4.54674 9.3413 4.815 9.44065 5.09344 9.43841C5.37189 9.43616 5.63825 9.33249 5.83515 9.14974C6.03205 8.96698 6.14373 8.71976 6.14615 8.46131C6.14857 8.20287 6.04153 7.95388 5.84808 7.76797L3.6243 5.7059H15.0937C15.8316 5.7059 16.5393 5.97799 17.0611 6.4623C17.5829 6.94662 17.876 7.60349 17.876 8.28842C17.876 8.97335 17.5829 9.63022 17.0611 10.1145C16.5393 10.5989 15.8316 10.8709 15.0937 10.8709V12.8423C16.3949 12.8423 17.6429 12.3625 18.563 11.5085C19.4831 10.6545 20 9.49619 20 8.28842C20 7.08065 19.4831 5.92234 18.563 5.06832C17.6429 4.2143 16.3949 3.73451 15.0937 3.73451Z"
fill={theme.palette.text.primary}
/>
</svg>
);
};

View File

@@ -0,0 +1,26 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const SaveIcon: React.FC<SVGProps> = ({ color, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
return (
<svg
{...children}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.18182 0C0.976833 0 0 0.976833 0 2.18182V21.8182C0 23.0232 0.976833 24 2.18182 24H21.8182C23.0232 24 24 23.0232 24 21.8182V7.4492C24 6.87053 23.7701 6.31559 23.3609 5.90641L18.0936 0.639044C17.6844 0.229866 17.1295 0 16.5508 0H16.3636C15.7611 0 15.2727 0.488422 15.2727 1.09091V5.45455C15.2727 6.65953 14.2959 7.63636 13.0909 7.63636H6.54545C5.34047 7.63636 4.36364 6.65953 4.36364 5.45455V1.09091C4.36364 0.488422 3.87521 0 3.27273 0H2.18182ZM12 18.5455C13.8075 18.5455 15.2727 17.0803 15.2727 15.2727C15.2727 13.4652 13.8075 12 12 12C10.1925 12 8.72727 13.4652 8.72727 15.2727C8.72727 17.0803 10.1925 18.5455 12 18.5455Z"
fill={setColor}
/>
</svg>
);
};

View File

@@ -0,0 +1,26 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Search: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z"
fill={setColor}
opacity={setOpacity}
/>
</svg>
);
};

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { styled } from '@mui/system';
import { SVGProps } from './interfaces';
import { useTheme } from '@mui/material';
// Make SvgContainer accept a prop
const SvgContainer = styled('svg')<{ color?: string }>(({ color }) => ({
'& path': {
fill: color,
},
}));
export const SendNewMessage: React.FC<SVGProps> = ({ color, ...props }) => {
const theme = useTheme();
const setColor = color || theme.palette.text.primary;
return (
<SvgContainer
{...props}
color={setColor}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.33271 10.2306C2.88006 10.001 2.89088 9.65814 3.3554 9.46527L16.3563 4.06742C16.8214 3.87427 17.0961 4.11004 16.9689 4.59692L14.1253 15.4847C13.9985 15.9703 13.5515 16.1438 13.1241 15.8705L10.0773 13.9219C9.8629 13.7848 9.56272 13.8345 9.40985 14.0292L8.41215 15.2997C8.10197 15.6946 7.71724 15.6311 7.5525 15.1567L6.67584 12.6326C6.51125 12.1587 6.01424 11.5902 5.55821 11.359L3.33271 10.2306Z"
/>
</SvgContainer>
);
};

View File

@@ -0,0 +1,37 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const SortIcon: React.FC<SVGProps> = ({
color,
height = 16,
width = 15,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width={width}
height={height}
viewBox="0 0 15 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.3347 0.271977C14.0797 0.0885134 13.79 0 13.5034 0C13.0191 0 12.5424 0.251056 12.2542 0.711326L12.0008 1.11366L10.6942 3.20097L9.44204 5.19976C9.15388 5.66003 9 6.19916 9 6.75116V14.3987C9 15.2822 9.67136 16 10.4996 16C10.9145 16 11.2902 15.8214 11.5602 15.5301C11.8318 15.2404 11.9992 14.8397 11.9992 14.3987V7.57353C11.9992 7.11809 12.1275 6.6723 12.3628 6.29411L14.7465 2.48964C14.917 2.21605 15 1.90706 15 1.60129C15 1.08469 14.7646 0.577751 14.3332 0.270368L14.3347 0.271977Z"
fill={setColor}
opacity={setOpacity}
/>
<path
d="M4.30727 3.20032L3.00075 1.11344L2.74881 0.711183C2.46065 0.251006 1.98391 0 1.49962 0C1.21297 0 0.923309 0.0884956 0.668343 0.271923C0.235353 0.579244 0 1.08608 0 1.60257C0 1.90829 0.0829771 2.21722 0.254966 2.49075L2.63716 6.29445C2.87403 6.67257 3.00075 7.11826 3.00075 7.57361V14.399C3.00075 15.2824 3.67211 16 4.50038 16C5.32864 16 6 15.2824 6 14.399V6.75141C6 6.19952 5.84762 5.6605 5.55947 5.20032L4.30576 3.20193L4.30727 3.20032Z"
fill={setColor}
opacity={setOpacity}
/>
</svg>
);
};

View File

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

View File

@@ -1,6 +1,9 @@
import React from "react";
import { useTheme } from '@mui/material';
export const StarFilledIcon = () => {
const theme = useTheme();
const setColor = theme.palette.text.primary;
return (
<svg
width="12"
@@ -11,7 +14,7 @@ export const StarFilledIcon = () => {
>
<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="white"
fill={setColor}
/>
</svg>
);

View File

@@ -0,0 +1,32 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const SuccessIcon: React.FC<SVGProps> = ({
color,
height = 155,
width = 156,
opacity,
...children
}) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width={width}
height={height}
viewBox="0 0 156 155"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M78 0C57.4456 0 37.7349 8.16507 23.1984 22.6984C8.66507 37.2332 0.5 56.9446 0.5 77.5C0.5 98.0554 8.66507 117.765 23.1984 132.302C37.7332 146.835 57.4445 155 78 155C98.5554 155 118.265 146.835 132.802 132.302C147.335 117.767 155.5 98.0554 155.5 77.5C155.48 56.9522 147.308 37.2523 132.779 22.7227C118.249 8.19318 98.5489 0.0215072 78.0014 0.00138561L78 0ZM66.5377 111.48L29.1001 77.2273L39.5907 65.765L66.0523 89.992L115.768 40.2557L126.764 51.2517L66.5377 111.48Z"
fill={setColor}
opacity={setOpacity}
/>
</svg>
);
};

View File

@@ -1,13 +1,18 @@
import React from 'react';
export const ThreadsIcon= ({ color = 'white', height = 11, width = 15 }) => {
return (
<svg width={width} height={height} viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z" fill={color}/>
</svg>
);
};
export const ThreadsIcon = ({ color = 'white', height = 11, width = 15 }) => {
return (
<svg
width={width}
height={height}
viewBox="0 0 15 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z"
fill={color}
/>
</svg>
);
};

View File

@@ -1,11 +1,16 @@
import React from 'react';
export const TradingIcon= ({ color, height, width }) => {
return (
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.6627 28.5686H5.6079C5.06735 28.5647 4.53939 28.4034 4.08736 28.104L8.41371 24.3903C8.68787 24.1495 9.01146 23.9731 9.36122 23.8737C9.71097 23.7744 10.0782 23.7546 10.4364 23.8157L18.8398 25.2345C19.5576 25.3564 20.294 25.3032 20.9873 25.0792C21.6806 24.8552 22.3109 24.467 22.8255 23.947L29.617 17.1085C29.8031 16.9068 29.9044 16.64 29.8996 16.3643C29.8948 16.0886 29.7842 15.8256 29.5912 15.6307C29.3982 15.4357 29.1378 15.324 28.8649 15.3192C28.5919 15.3143 28.3278 15.4166 28.1281 15.6046L21.3225 22.4537C21.047 22.7365 20.7084 22.9485 20.3351 23.0719C19.9618 23.1953 19.5646 23.2266 19.1769 23.1631L10.7876 21.7195C10.1245 21.6058 9.44477 21.6422 8.79738 21.8263C8.15 22.0103 7.55117 22.3373 7.04417 22.7836L2.87233 26.3624C2.82441 26.1554 2.79968 25.9437 2.79859 25.7311V24.3123L7.18463 16.8389C7.44944 16.3923 7.84654 16.041 8.3198 15.8348C8.79305 15.6286 9.31851 15.5778 9.82188 15.6897L17.5299 17.3745C18.1946 17.5208 18.8833 17.5152 19.5455 17.358C20.2078 17.2009 20.8269 16.8963 21.3576 16.4665L29.5327 9.8408C29.6456 9.75519 29.7402 9.64756 29.8111 9.52429C29.8819 9.40103 29.9275 9.26464 29.9452 9.12323C29.9629 8.98181 29.9522 8.83826 29.9139 8.70108C29.8756 8.5639 29.8104 8.4359 29.7221 8.32467C29.6339 8.21344 29.5244 8.12125 29.4002 8.05357C29.276 7.98589 29.1396 7.9441 28.9991 7.93069C28.8587 7.91727 28.7169 7.9325 28.5824 7.97547C28.4478 8.01844 28.3232 8.08828 28.2159 8.18084L20.0408 14.8065C19.755 15.0381 19.4216 15.2023 19.065 15.2869C18.7083 15.3716 18.3374 15.3747 17.9794 15.296L10.2714 13.597C9.33145 13.387 8.34986 13.4825 7.46689 13.8698C6.58392 14.2571 5.84477 14.9164 5.35506 15.7535L2.79859 20.1411V1.74669C2.79859 1.46448 2.68759 1.19383 2.49003 0.994272C2.29246 0.794718 2.0245 0.68261 1.74509 0.68261C1.46569 0.68261 1.19773 0.794718 1.00016 0.994272C0.802591 1.19383 0.691598 1.46448 0.691598 1.74669V25.7311C0.689568 26.7954 1.02832 27.8318 1.6573 28.6857C1.6573 28.707 1.68539 28.7318 1.70295 28.7531C2.1613 29.3522 2.74923 29.8376 3.42178 30.172C4.09433 30.5064 4.83369 30.6811 5.58332 30.6826H29.6381C29.9175 30.6826 30.1855 30.5705 30.383 30.3709C30.5806 30.1714 30.6916 29.9007 30.6916 29.6185C30.6916 29.3363 30.5806 29.0657 30.383 28.8661C30.1855 28.6666 29.9175 28.5544 29.6381 28.5544L29.6627 28.5686Z" fill={color}/>
</svg>
);
};
export const TradingIcon = ({ color, height, width }) => {
return (
<svg
width="31"
height="31"
viewBox="0 0 31 31"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M29.6627 28.5686H5.6079C5.06735 28.5647 4.53939 28.4034 4.08736 28.104L8.41371 24.3903C8.68787 24.1495 9.01146 23.9731 9.36122 23.8737C9.71097 23.7744 10.0782 23.7546 10.4364 23.8157L18.8398 25.2345C19.5576 25.3564 20.294 25.3032 20.9873 25.0792C21.6806 24.8552 22.3109 24.467 22.8255 23.947L29.617 17.1085C29.8031 16.9068 29.9044 16.64 29.8996 16.3643C29.8948 16.0886 29.7842 15.8256 29.5912 15.6307C29.3982 15.4357 29.1378 15.324 28.8649 15.3192C28.5919 15.3143 28.3278 15.4166 28.1281 15.6046L21.3225 22.4537C21.047 22.7365 20.7084 22.9485 20.3351 23.0719C19.9618 23.1953 19.5646 23.2266 19.1769 23.1631L10.7876 21.7195C10.1245 21.6058 9.44477 21.6422 8.79738 21.8263C8.15 22.0103 7.55117 22.3373 7.04417 22.7836L2.87233 26.3624C2.82441 26.1554 2.79968 25.9437 2.79859 25.7311V24.3123L7.18463 16.8389C7.44944 16.3923 7.84654 16.041 8.3198 15.8348C8.79305 15.6286 9.31851 15.5778 9.82188 15.6897L17.5299 17.3745C18.1946 17.5208 18.8833 17.5152 19.5455 17.358C20.2078 17.2009 20.8269 16.8963 21.3576 16.4665L29.5327 9.8408C29.6456 9.75519 29.7402 9.64756 29.8111 9.52429C29.8819 9.40103 29.9275 9.26464 29.9452 9.12323C29.9629 8.98181 29.9522 8.83826 29.9139 8.70108C29.8756 8.5639 29.8104 8.4359 29.7221 8.32467C29.6339 8.21344 29.5244 8.12125 29.4002 8.05357C29.276 7.98589 29.1396 7.9441 28.9991 7.93069C28.8587 7.91727 28.7169 7.9325 28.5824 7.97547C28.4478 8.01844 28.3232 8.08828 28.2159 8.18084L20.0408 14.8065C19.755 15.0381 19.4216 15.2023 19.065 15.2869C18.7083 15.3716 18.3374 15.3747 17.9794 15.296L10.2714 13.597C9.33145 13.387 8.34986 13.4825 7.46689 13.8698C6.58392 14.2571 5.84477 14.9164 5.35506 15.7535L2.79859 20.1411V1.74669C2.79859 1.46448 2.68759 1.19383 2.49003 0.994272C2.29246 0.794718 2.0245 0.68261 1.74509 0.68261C1.46569 0.68261 1.19773 0.794718 1.00016 0.994272C0.802591 1.19383 0.691598 1.46448 0.691598 1.74669V25.7311C0.689568 26.7954 1.02832 27.8318 1.6573 28.6857C1.6573 28.707 1.68539 28.7318 1.70295 28.7531C2.1613 29.3522 2.74923 29.8376 3.42178 30.172C4.09433 30.5064 4.83369 30.6811 5.58332 30.6826H29.6381C29.9175 30.6826 30.1855 30.5705 30.383 30.3709C30.5806 30.1714 30.6916 29.9007 30.6916 29.6185C30.6916 29.3363 30.5806 29.0657 30.383 28.8661C30.1855 28.6666 29.9175 28.5544 29.6381 28.5544L29.6627 28.5686Z"
fill={color}
/>
</svg>
);
};

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,9 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="20" height="20" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_127_477" transform="scale(0.015625)"/>
</pattern>
<image id="image0_127_477" width="64" height="64" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAACW0lEQVR4nO3bu2sUURSA8RsVMQRRfBArQ3yAb8RC0MZKsLBRtLHQQvEfEBEVS+2sjQHjo1TERhCLNHZWVsFGxShiFLTygSj+5OIKm2E3m12Tnbk789V7z+53zpl7z87uhFBRUZEiWIi9uIQx3MUozmMX+kIvgkU4iUkz8xxHeyoRWIlx7fEQy0LqYBivdMYzLA2Jy082EPuCERzDfpzCffxs8NoHSV4Omss/wmCTNTsw0WDNkdAj8jewoMXa5Q2SMJFMF2guP9ZKPtMJvzLrd4aE5W/OVr4uVtwT6jkXEpW/1a58LV7cGOsZCQnK34mTX4cxD2Ri3QuJyU9h4D/ixiOyntGQkHz9kbekw9jXTediSEy+4yRgDb6azu6QoHzbSYgbJh6bzstONtJ5AUMdzvZRqr9F7D5ca7D2eEi08rPuhFrl47CUZbzTk6Ro8k2TUJOPw1KW2GmrQsJt3/JyqMnHYSnLG6zvRfn6JAwUXX54jtq+GXFYyhLfb10vV34mClP5oUq+u7wtu/yGvN1DJa+qfDcpddu/x+a83UMlr6p8Nyl120+VXX5L3u6hkldVvptUbR/yRrXhyWO331qEyq/G6xyGnE2hCOBsDvL5Dzn/qP23rlt8KETbZ35X+1RK+UgcObsovy0UDZwurXwEt0srH8GLMssPllY+gsPzJP+x8PIRXJ0n+e0hBfC0zPL9+FFK+Qj2lVY+ggullZ+DL0Df8QSXsTakiL/ndDs3LuIzOGewB4tD6uBdE9nftUdN4kOJJ7Ax9CI4hM/4VmvnKziIFXl/toqKMG/8Ad4tdVYcSnGDAAAAAElFTkSuQmCC"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +0,0 @@
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.92857 0.5H8.57143C9.36071 0.5 10 1.13929 10 1.92857V6.57143C10 7.36071 9.36071 8 8.57143 8H8.21429V4.42857C8.21429 3.24643 7.25357 2.28571 6.07143 2.28571H2.5V1.92857C2.5 1.13929 3.13929 0.5 3.92857 0.5ZM1.42857 3H6.07143C6.86041 3 7.5 3.63959 7.5 4.42857V9.07143C7.5 9.86041 6.86041 10.5 6.07143 10.5H1.42857C0.639593 10.5 0 9.86041 0 9.07143V4.42857C0 3.63959 0.639593 3 1.42857 3Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

View File

@@ -1,20 +0,0 @@
import React from 'react';
import { styled } from '@mui/system';
import { SVGProps } from './interfaces';
// Create a styled container with hover effects
const SvgContainer = styled('svg')({
'& path': {
fill: 'rgba(41, 41, 43, 1)', // Default to red if no color prop
}
});
export const CreateThreadIcon:React.FC<SVGProps> = ({ color, opacity }) => {
return (
<SvgContainer width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path 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"/>
</SvgContainer>
);
};

View File

@@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8047 0.393196V7.21185H16.3036L10.0003 13.5139L3.69697 7.21185H7.19584V0H12.8045L12.8047 0.393196ZM2.7047 16.8587V13.9861H0V18.6179C0 19.3774 0.622589 20 1.38213 20H18.6179C19.3774 20 20 19.3774 20 18.6179V13.9861H17.2962V17.2963L2.70461 17.2954L2.7047 16.8587Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 452 B

View File

@@ -1,3 +0,0 @@
<svg width="15" height="11" viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76526 1.80943H6.51017C5.91236 1.80943 5.42723 2.37147 5.42723 3.06406V6.55419V6.82615H4L1.90297 9L2.00782 6.82615H0.8482C0.380282 6.82615 0 6.38558 0 5.84347V0.982675C0 0.440572 0.380282 0 0.8482 0H7.1518C7.62128 0 8 0.440572 8 0.982675V1.80943H7.76526ZM8.89437 2H14.0458C14.5722 2 15 2.44057 15 2.98268V7.84166C15 8.38558 14.5722 8.82434 14.0458 8.82434H12.7412L12.8592 11L10.5 8.82434H6.95423C6.42606 8.82434 6 8.38558 6 7.84166V7.01672V6.74476V6.47281V2.98268C6 2.44057 6.42606 2 6.95423 2H8.3662H8.63028H8.89437Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 688 B

View File

@@ -1,3 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 0C4.06452 0 0 4.06452 0 9C0 13.9355 4.06452 18 9 18C13.9355 18 18 13.9355 18 9C18 4.06452 13.8629 0 9 0ZM9 16.8387C4.71774 16.8387 1.16129 13.2823 1.16129 9C1.16129 4.71774 4.71774 1.16129 9 1.16129C13.2823 1.16129 16.8387 4.71774 16.8387 9C16.8387 13.2823 13.2823 16.8387 9 16.8387ZM9.79861 13.2097C9.79861 13.5726 9.50829 13.9355 9.0728 13.9355H8.85506C8.49216 13.9355 8.12926 13.6451 8.12926 13.2097V7.18545C8.12926 6.82255 8.41958 6.45965 8.85506 6.45965H9.0728C9.43571 6.45965 9.79861 6.74997 9.79861 7.18545V13.2097ZM9.79823 4.86286C9.79823 5.30379 9.44078 5.66125 8.99984 5.66125C8.5589 5.66125 8.20145 5.30379 8.20145 4.86286C8.20145 4.42192 8.5589 4.06447 8.99984 4.06447C9.44078 4.06447 9.79823 4.42192 9.79823 4.86286Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 920 B

View File

@@ -1,3 +0,0 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.56485 0H16.3611C17.2662 0 18 0.727797 18 1.62558V18.3744C18 19.2722 17.2662 20 16.3611 20H7.56485C6.65969 20 5.92593 19.2722 5.92593 18.3744V12.6013H10.6168C11.4569 12.6013 12.1404 11.9039 12.1404 11.0467V8.87329C12.1404 8.01613 11.4569 7.31875 10.6168 7.31875H5.92593V1.62558C5.92593 0.727797 6.65969 0 7.56485 0ZM11.1667 11.0467C11.1667 11.3719 10.9205 11.6354 10.6168 11.6354H4.8144C4.74521 11.6354 4.68911 11.6955 4.68911 11.7696V12.8632C4.68911 13.3492 4.17007 13.6259 3.8078 13.3329L0.218431 10.4298C-0.0728102 10.1942 -0.0728102 9.72579 0.218431 9.49024L3.8078 6.58709C4.17005 6.29409 4.68911 6.57077 4.68911 7.05684V8.1504C4.68911 8.2245 4.74521 8.28454 4.8144 8.28454H10.6168C10.9205 8.28454 11.1667 8.54813 11.1667 8.87329V11.0467Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 932 B

View File

@@ -1,3 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.14468 0L0.394043 2.66667L5.89531 8L0.394043 13.3333L3.14468 16L8.64594 10.6667L14.1472 16L16.8978 13.3333L11.3966 8L16.8978 2.66667L14.1472 0L8.64594 5.33333L3.14468 0Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 300 B

View File

@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="19.9999" r="18" fill="#434343"/>
<path d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 602 B

View File

@@ -1,10 +0,0 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_400_537)">
<path d="M28.3334 15.5833H11.0925L19.0117 7.6641L17 5.6666L5.66669 16.9999L17 28.3333L18.9975 26.3358L11.0925 18.4166H28.3334V15.5833Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_400_537">
<rect width="34" height="34" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 396 B

View File

@@ -1,6 +0,0 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8.5" cy="8.5" r="8.5" fill="white"/>
<circle cx="8.5" cy="8.50003" r="6.61111" fill="#434343"/>
<path d="M5.66675 5.66669L11.3334 11.3334" stroke="white" stroke-width="2"/>
<path d="M11.3333 5.66675L5.66658 11.3334" stroke="white" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 365 B

View File

@@ -1,10 +0,0 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_400_556)">
<path d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_400_556">
<rect width="34" height="34" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 874 B

View File

@@ -1,3 +0,0 @@
<svg 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="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,10 +0,0 @@
import React from 'react'
export const SaveIcon = ({color = '#8F8F91'}) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.18182 0C0.976833 0 0 0.976833 0 2.18182V21.8182C0 23.0232 0.976833 24 2.18182 24H21.8182C23.0232 24 24 23.0232 24 21.8182V7.4492C24 6.87053 23.7701 6.31559 23.3609 5.90641L18.0936 0.639044C17.6844 0.229866 17.1295 0 16.5508 0H16.3636C15.7611 0 15.2727 0.488422 15.2727 1.09091V5.45455C15.2727 6.65953 14.2959 7.63636 13.0909 7.63636H6.54545C5.34047 7.63636 4.36364 6.65953 4.36364 5.45455V1.09091C4.36364 0.488422 3.87521 0 3.27273 0H2.18182ZM12 18.5455C13.8075 18.5455 15.2727 17.0803 15.2727 15.2727C15.2727 13.4652 13.8075 12 12 12C10.1925 12 8.72727 13.4652 8.72727 15.2727C8.72727 17.0803 10.1925 18.5455 12 18.5455Z" fill={color}/>
</svg>
)
}

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 748 B

View File

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

View File

@@ -1,4 +0,0 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.3347 0.271977C14.0797 0.0885134 13.79 0 13.5034 0C13.0191 0 12.5424 0.251056 12.2542 0.711326L12.0008 1.11366L10.6942 3.20097L9.44204 5.19976C9.15388 5.66003 9 6.19916 9 6.75116V14.3987C9 15.2822 9.67136 16 10.4996 16C10.9145 16 11.2902 15.8214 11.5602 15.5301C11.8318 15.2404 11.9992 14.8397 11.9992 14.3987V7.57353C11.9992 7.11809 12.1275 6.6723 12.3628 6.29411L14.7465 2.48964C14.917 2.21605 15 1.90706 15 1.60129C15 1.08469 14.7646 0.577751 14.3332 0.270368L14.3347 0.271977Z" fill="white"/>
<path d="M4.30727 3.20032L3.00075 1.11344L2.74881 0.711183C2.46065 0.251006 1.98391 0 1.49962 0C1.21297 0 0.923309 0.0884956 0.668343 0.271923C0.235353 0.579244 0 1.08608 0 1.60257C0 1.90829 0.0829771 2.21722 0.254966 2.49075L2.63716 6.29445C2.87403 6.67257 3.00075 7.11826 3.00075 7.57361V14.399C3.00075 15.2824 3.67211 16 4.50038 16C5.32864 16 6 15.2824 6 14.399V6.75141C6 6.19952 5.84762 5.6605 5.55947 5.20032L4.30576 3.20193L4.30727 3.20032Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

View File

@@ -1,3 +0,0 @@
<svg width="156" height="155" viewBox="0 0 156 155" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M78 0C57.4456 0 37.7349 8.16507 23.1984 22.6984C8.66507 37.2332 0.5 56.9446 0.5 77.5C0.5 98.0554 8.66507 117.765 23.1984 132.302C37.7332 146.835 57.4445 155 78 155C98.5554 155 118.265 146.835 132.802 132.302C147.335 117.767 155.5 98.0554 155.5 77.5C155.48 56.9522 147.308 37.2523 132.779 22.7227C118.249 8.19318 98.5489 0.0215072 78.0014 0.00138561L78 0ZM66.5377 111.48L29.1001 77.2273L39.5907 65.765L66.0523 89.992L115.768 40.2557L126.764 51.2517L66.5377 111.48Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 615 B

View File

@@ -1,6 +0,0 @@
export interface SVGProps {
color: string
height: string
width: string
opacity?: number
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,4 +0,0 @@
<svg width="301" height="26" viewBox="0 0 301 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 15.3636H14.4091L16.1818 17.5909L19.1818 21L23.0909 25.7273H17.5L14.7273 22.5L12.8636 19.8182L9.5 15.3636ZM23.0455 12.3636C23.0455 14.9545 22.5417 17.1402 21.5341 18.9205C20.5265 20.6932 19.1667 22.0379 17.4545 22.9545C15.7424 23.8636 13.8333 24.3182 11.7273 24.3182C9.60606 24.3182 7.68939 23.8598 5.97727 22.9432C4.27273 22.0189 2.91667 20.6705 1.90909 18.8977C0.909091 17.1174 0.409091 14.9394 0.409091 12.3636C0.409091 9.77273 0.909091 7.59091 1.90909 5.81818C2.91667 4.03788 4.27273 2.69318 5.97727 1.78409C7.68939 0.867423 9.60606 0.40909 11.7273 0.40909C13.8333 0.40909 15.7424 0.867423 17.4545 1.78409C19.1667 2.69318 20.5265 4.03788 21.5341 5.81818C22.5417 7.59091 23.0455 9.77273 23.0455 12.3636ZM16.5455 12.3636C16.5455 10.9697 16.3598 9.79545 15.9886 8.84091C15.625 7.87879 15.0833 7.15151 14.3636 6.65909C13.6515 6.15909 12.7727 5.90909 11.7273 5.90909C10.6818 5.90909 9.79924 6.15909 9.07955 6.65909C8.36742 7.15151 7.82576 7.87879 7.45455 8.84091C7.09091 9.79545 6.90909 10.9697 6.90909 12.3636C6.90909 13.7576 7.09091 14.9356 7.45455 15.8977C7.82576 16.8523 8.36742 17.5795 9.07955 18.0795C9.79924 18.572 10.6818 18.8182 11.7273 18.8182C12.7727 18.8182 13.6515 18.572 14.3636 18.0795C15.0833 17.5795 15.625 16.8523 15.9886 15.8977C16.3598 14.9356 16.5455 13.7576 16.5455 12.3636ZM39.5455 10V14.7273H28.6364V10H39.5455ZM51.233 24H44.4148L52.0966 0.727272H60.733L68.4148 24H61.5966L56.5057 7.13636H56.3239L51.233 24ZM49.9602 14.8182H62.7784V19.5455H49.9602V14.8182ZM72.6562 24V0.727272H82.7017C84.429 0.727272 85.9403 1.06818 87.2358 1.75C88.5313 2.43182 89.5388 3.39015 90.2585 4.625C90.9782 5.85985 91.3381 7.30303 91.3381 8.95455C91.3381 10.6212 90.9669 12.0644 90.2244 13.2841C89.4896 14.5038 88.4555 15.4432 87.1222 16.1023C85.7964 16.7614 84.2472 17.0909 82.4744 17.0909H76.4744V12.1818H81.2017C81.9441 12.1818 82.5767 12.053 83.0994 11.7955C83.6297 11.5303 84.035 11.1553 84.3153 10.6705C84.6032 10.1856 84.7472 9.61364 84.7472 8.95455C84.7472 8.28788 84.6032 7.7197 84.3153 7.25C84.035 6.77273 83.6297 6.40909 83.0994 6.15909C82.5767 5.90151 81.9441 5.77273 81.2017 5.77273H78.9744V24H72.6562ZM95.6562 24V0.727272H105.702C107.429 0.727272 108.94 1.06818 110.236 1.75C111.531 2.43182 112.539 3.39015 113.259 4.625C113.978 5.85985 114.338 7.30303 114.338 8.95455C114.338 10.6212 113.967 12.0644 113.224 13.2841C112.49 14.5038 111.455 15.4432 110.122 16.1023C108.796 16.7614 107.247 17.0909 105.474 17.0909H99.4744V12.1818H104.202C104.944 12.1818 105.577 12.053 106.099 11.7955C106.63 11.5303 107.035 11.1553 107.315 10.6705C107.603 10.1856 107.747 9.61364 107.747 8.95455C107.747 8.28788 107.603 7.7197 107.315 7.25C107.035 6.77273 106.63 6.40909 106.099 6.15909C105.577 5.90151 104.944 5.77273 104.202 5.77273H101.974V24H95.6562ZM131.202 8C131.141 7.24242 130.857 6.65151 130.349 6.22727C129.849 5.80303 129.088 5.59091 128.065 5.59091C127.414 5.59091 126.88 5.67045 126.463 5.82954C126.054 5.98106 125.751 6.18939 125.554 6.45454C125.357 6.7197 125.255 7.02273 125.247 7.36364C125.232 7.64394 125.281 7.89773 125.395 8.125C125.516 8.3447 125.705 8.54545 125.963 8.72727C126.221 8.90151 126.55 9.06061 126.952 9.20455C127.353 9.34848 127.83 9.47727 128.384 9.59091L130.293 10C131.58 10.2727 132.683 10.6326 133.599 11.0795C134.516 11.5265 135.266 12.053 135.849 12.6591C136.433 13.2576 136.861 13.9318 137.134 14.6818C137.414 15.4318 137.558 16.25 137.565 17.1364C137.558 18.6667 137.175 19.9621 136.418 21.0227C135.66 22.0833 134.577 22.8902 133.168 23.4432C131.766 23.9962 130.08 24.2727 128.111 24.2727C126.088 24.2727 124.323 23.9735 122.815 23.375C121.315 22.7765 120.149 21.8561 119.315 20.6136C118.49 19.3636 118.073 17.7652 118.065 15.8182H124.065C124.103 16.5303 124.281 17.1288 124.599 17.6136C124.918 18.0985 125.365 18.4659 125.94 18.7159C126.524 18.9659 127.217 19.0909 128.02 19.0909C128.694 19.0909 129.259 19.0076 129.713 18.8409C130.168 18.6742 130.512 18.4432 130.747 18.1477C130.982 17.8523 131.103 17.5152 131.111 17.1364C131.103 16.7803 130.986 16.4697 130.759 16.2045C130.539 15.9318 130.175 15.6894 129.668 15.4773C129.16 15.2576 128.474 15.053 127.611 14.8636L125.293 14.3636C123.232 13.9167 121.607 13.1705 120.418 12.125C119.236 11.072 118.649 9.63636 118.656 7.81818C118.649 6.34091 119.043 5.04924 119.838 3.94318C120.641 2.82954 121.751 1.96212 123.168 1.34091C124.592 0.719696 126.224 0.40909 128.065 0.40909C129.944 0.40909 131.569 0.723484 132.94 1.35227C134.312 1.98106 135.368 2.86742 136.111 4.01136C136.861 5.14773 137.24 6.47727 137.247 8H131.202Z" fill="white"/>
<path d="M150.344 24V0.727272H156.662V18.9091H166.071V24H150.344ZM176.943 0.727272V24H170.625V0.727272H176.943ZM181.938 24V0.727272H192.028C193.801 0.727272 195.29 0.965909 196.494 1.44318C197.706 1.92045 198.619 2.5947 199.233 3.46591C199.854 4.33712 200.165 5.36364 200.165 6.54545C200.165 7.40151 199.975 8.18182 199.597 8.88636C199.225 9.59091 198.703 10.1818 198.028 10.6591C197.354 11.1288 196.566 11.4545 195.665 11.6364V11.8636C196.665 11.9015 197.574 12.1553 198.392 12.625C199.21 13.0871 199.862 13.7273 200.347 14.5455C200.831 15.3561 201.074 16.3106 201.074 17.4091C201.074 18.6818 200.741 19.8144 200.074 20.8068C199.415 21.7992 198.475 22.5795 197.256 23.1477C196.036 23.7159 194.581 24 192.892 24H181.938ZM188.256 18.9545H191.21C192.271 18.9545 193.066 18.7576 193.597 18.3636C194.127 17.9621 194.392 17.3712 194.392 16.5909C194.392 16.0455 194.267 15.5833 194.017 15.2045C193.767 14.8258 193.411 14.5379 192.949 14.3409C192.494 14.1439 191.945 14.0455 191.301 14.0455H188.256V18.9545ZM188.256 10.1364H190.847C191.4 10.1364 191.888 10.0492 192.312 9.875C192.737 9.70076 193.066 9.45076 193.301 9.125C193.544 8.79167 193.665 8.38636 193.665 7.90909C193.665 7.18939 193.407 6.64015 192.892 6.26136C192.377 5.875 191.725 5.68182 190.938 5.68182H188.256V10.1364ZM205.312 24V0.727272H215.358C217.085 0.727272 218.597 1.04167 219.892 1.67045C221.188 2.29924 222.195 3.20455 222.915 4.38636C223.634 5.56818 223.994 6.98485 223.994 8.63636C223.994 10.303 223.623 11.7083 222.881 12.8523C222.146 13.9962 221.112 14.8598 219.778 15.4432C218.453 16.0265 216.903 16.3182 215.131 16.3182H209.131V11.4091H213.858C214.6 11.4091 215.233 11.3182 215.756 11.1364C216.286 10.947 216.691 10.6477 216.972 10.2386C217.259 9.82955 217.403 9.29545 217.403 8.63636C217.403 7.9697 217.259 7.42803 216.972 7.01136C216.691 6.58712 216.286 6.27651 215.756 6.07954C215.233 5.875 214.6 5.77273 213.858 5.77273H211.631V24H205.312ZM218.949 13.3182L224.767 24H217.903L212.222 13.3182H218.949ZM234.733 24H227.915L235.597 0.727272H244.233L251.915 24H245.097L240.006 7.13636H239.824L234.733 24ZM233.46 14.8182H246.278V19.5455H233.46V14.8182ZM256.156 24V0.727272H266.202C267.929 0.727272 269.44 1.04167 270.736 1.67045C272.031 2.29924 273.039 3.20455 273.759 4.38636C274.478 5.56818 274.838 6.98485 274.838 8.63636C274.838 10.303 274.467 11.7083 273.724 12.8523C272.99 13.9962 271.955 14.8598 270.622 15.4432C269.296 16.0265 267.747 16.3182 265.974 16.3182H259.974V11.4091H264.702C265.444 11.4091 266.077 11.3182 266.599 11.1364C267.13 10.947 267.535 10.6477 267.815 10.2386C268.103 9.82955 268.247 9.29545 268.247 8.63636C268.247 7.9697 268.103 7.42803 267.815 7.01136C267.535 6.58712 267.13 6.27651 266.599 6.07954C266.077 5.875 265.444 5.77273 264.702 5.77273H262.474V24H256.156ZM269.793 13.3182L275.611 24H268.747L263.065 13.3182H269.793ZM277.483 0.727272H284.528L289.074 10.1818H289.256L293.801 0.727272H300.847L292.301 16.6818V24H286.028V16.6818L277.483 0.727272Z" fill="#0091E1"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,194 +1,84 @@
import { atom, selectorFamily } from 'recoil';
import { atom } from 'jotai';
import { atomWithReset, atomFamily } from 'jotai/utils';
// Atoms (resettable)
export const sortablePinnedAppsAtom = atomWithReset([
{ name: 'Q-Tube', service: 'APP' },
{ name: 'Q-Mail', service: 'APP' },
{ name: 'Q-Share', service: 'APP' },
{ name: 'Q-Fund', service: 'APP' },
{ name: 'Q-Shop', service: 'APP' },
{ name: 'Q-Trade', service: 'APP' },
{ name: 'Q-Support', service: 'APP' },
{ name: 'Q-Manager', service: 'APP' },
{ name: 'Q-Blog', service: 'APP' },
{ name: 'Q-Mintership', service: 'APP' },
{ name: 'Q-Wallets', service: 'APP' },
{ name: 'Q-Search', service: 'APP' },
{ name: 'Q-Nodecontrol', service: 'APP' },
]);
export const sortablePinnedAppsAtom = atom({
key: 'sortablePinnedAppsFromAtom',
default: [
{
name: 'Q-Tube',
service: 'APP',
},
{
name: 'Q-Mail',
service: 'APP',
},
{
name: 'Q-Share',
service: 'APP',
},
{
name: 'Q-Fund',
service: 'APP',
},
{
name: 'Q-Shop',
service: 'APP',
},
{
name: 'Q-Trade',
service: 'APP',
},
{
name: 'Q-Support',
service: 'APP',
},
{
name: 'Q-Manager',
service: 'APP',
},
{
name: 'Q-Blog',
service: 'APP',
},
{
name: 'Q-Mintership',
service: 'APP',
},
{
name: 'Q-Wallets',
service: 'APP',
},
{
name: 'Q-Search',
service: 'APP',
},
{
name: 'Q-Nodecontrol',
service: 'APP'
}
],
});
export const canSaveSettingToQdnAtom = atomWithReset(false);
export const settingsQDNLastUpdatedAtom = atomWithReset(-100);
export const settingsLocalLastUpdatedAtom = atomWithReset(0);
export const oldPinnedAppsAtom = atomWithReset([]);
export const isUsingImportExportSettingsAtom = atomWithReset(null);
export const fullScreenAtom = atomWithReset(false);
export const hasSettingsChangedAtom = atomWithReset(false);
export const navigationControllerAtom = atomWithReset({});
export const enabledDevModeAtom = atomWithReset(false);
export const myGroupsWhereIAmAdminAtom = atomWithReset([]);
export const promotionTimeIntervalAtom = atomWithReset(0);
export const promotionsAtom = atomWithReset([]);
export const resourceDownloadControllerAtom = atomWithReset({});
export const blobControllerAtom = atomWithReset({});
export const selectedGroupIdAtom = atomWithReset(null);
export const addressInfoControllerAtom = atomWithReset({});
export const isDisabledEditorEnterAtom = atomWithReset(false);
export const qMailLastEnteredTimestampAtom = atomWithReset(null);
export const lastPaymentSeenTimestampAtom = atomWithReset(null);
export const mailsAtom = atomWithReset([]);
export const groupsPropertiesAtom = atomWithReset({});
export const groupsOwnerNamesAtom = atomWithReset({});
export const isOpenBlockedModalAtom = atomWithReset(false);
export const groupAnnouncementsAtom = atomWithReset({});
export const mutedGroupsAtom = atomWithReset([]);
export const groupChatTimestampsAtom = atomWithReset({});
export const timestampEnterDataAtom = atomWithReset({});
export const canSaveSettingToQdnAtom = atom({
key: 'canSaveSettingToQdnAtom',
default: false,
});
export const txListAtom = atomWithReset([]);
export const memberGroupsAtom = atomWithReset([]);
export const isRunningPublicNodeAtom = atomWithReset(false);
export const settingsQDNLastUpdatedAtom = atom({
key: 'settingsQDNLastUpdatedAtom',
default: -100,
});
// Atom Families (replacing selectorFamily)
export const resourceKeySelector = atomFamily((key) =>
atom((get) => get(resourceDownloadControllerAtom)[key] || null)
);
export const settingsLocalLastUpdatedAtom = atom({
key: 'settingsLocalLastUpdatedAtom',
default: 0,
});
export const blobKeySelector = atomFamily((key) =>
atom((get) => get(blobControllerAtom)[key] || null)
);
export const oldPinnedAppsAtom = atom({
key: 'oldPinnedAppsAtom',
default: [],
});
export const isUsingImportExportSettingsAtom = atom({
key: 'isUsingImportExportSettingsAtom',
default: null,
});
export const addressInfoKeySelector = atomFamily((key) =>
atom((get) => get(addressInfoControllerAtom)[key] || null)
);
export const groupsOwnerNamesSelector = atomFamily((key) =>
atom((get) => get(groupsOwnerNamesAtom)[key] || null)
);
export const fullScreenAtom = atom({
key: 'fullScreenAtom',
default: false,
});
export const groupAnnouncementSelector = atomFamily((key) =>
atom((get) => get(groupAnnouncementsAtom)[key] || null)
);
export const hasSettingsChangedAtom = atom({
key: 'hasSettingsChangedAtom',
default: false,
});
export const groupPropertySelector = atomFamily((key) =>
atom((get) => get(groupsPropertiesAtom)[key] || null)
);
export const navigationControllerAtom = atom({
key: 'navigationControllerAtom',
default: {},
});
export const groupChatTimestampSelector = atomFamily((key) =>
atom((get) => get(groupChatTimestampsAtom)[key] || null)
);
export const enabledDevModeAtom = atom({
key: 'enabledDevModeAtom',
default: false,
});
export const myGroupsWhereIAmAdminAtom = atom({
key: 'myGroupsWhereIAmAdminAtom',
default: [],
});
export const promotionTimeIntervalAtom = atom({
key: 'promotionTimeIntervalAtom',
default: 0,
});
export const promotionsAtom = atom({
key: 'promotionsAtom',
default: [],
});
export const resourceDownloadControllerAtom = atom({
key: 'resourceDownloadControllerAtom',
default: {},
});
export const resourceKeySelector = selectorFamily({
key: 'resourceKeySelector',
get: (key) => ({ get }) => {
const resources = get(resourceDownloadControllerAtom);
return resources[key] || null; // Return the value for the key or null if not found
},
});
export const blobControllerAtom = atom({
key: 'blobControllerAtom',
default: {},
});
export const blobKeySelector = selectorFamily({
key: 'blobKeySelector',
get: (key) => ({ get }) => {
const blobs = get(blobControllerAtom);
return blobs[key] || null; // Return the value for the key or null if not found
},
});
export const selectedGroupIdAtom = atom({
key: 'selectedGroupIdAtom',
default: null,
});
export const addressInfoControllerAtom = atom({
key: 'addressInfoControllerAtom',
default: {},
});
export const addressInfoKeySelector = selectorFamily({
key: 'addressInfoKeySelector',
get: (key) => ({ get }) => {
const userInfo = get(addressInfoControllerAtom);
return userInfo[key] || null; // Return the value for the key or null if not found
},
});
export const isDisabledEditorEnterAtom = atom({
key: 'isDisabledEditorEnterAtom',
default: false,
});
export const qMailLastEnteredTimestampAtom = atom({
key: 'qMailLastEnteredTimestampAtom',
default: null,
});
export const lastPaymentSeenTimestampAtom = atom<null | number>({
key: 'lastPaymentSeenTimestampAtom',
default: null,
});
export const mailsAtom = atom({
key: 'mailsAtom',
default: [],
});
export const groupsPropertiesAtom = atom({
key: 'groupsPropertiesAtom',
default: {},
});
export const isOpenBlockedModalAtom = atom({
key: 'isOpenBlockedModalAtom',
default: false,
});
export const timestampEnterDataSelector = atomFamily((key) =>
atom((get) => get(timestampEnterDataAtom)[key] || null)
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,327 +1,368 @@
import { getBaseApi } from "../background";
import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64 } from "../qdn/encryption/group-encryption";
import { publishData } from "../qdn/publish/pubish";
import { getData } from "../utils/chromeStorage";
import { RequestQueueWithPromise } from "../utils/queue/queue";
import { getBaseApi } from '../background';
import {
createSymmetricKeyAndNonce,
decryptGroupData,
encryptDataGroup,
objectToBase64,
} from '../qdn/encryption/group-encryption';
import { publishData } from '../qdn/publish/pubish';
import { getData } from '../utils/chromeStorage';
import { RequestQueueWithPromise } from '../utils/queue/queue';
export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10);
const apiEndpoints = [
"https://api.qortal.org",
"https://api2.qortal.org",
"https://appnode.qortal.org",
"https://apinode.qortalnodes.live",
"https://apinode1.qortalnodes.live",
"https://apinode2.qortalnodes.live",
"https://apinode3.qortalnodes.live",
"https://apinode4.qortalnodes.live",
'https://api.qortal.org',
'https://api2.qortal.org',
'https://appnode.qortal.org',
'https://apinode.qortalnodes.live',
'https://apinode1.qortalnodes.live',
'https://apinode2.qortalnodes.live',
'https://apinode3.qortalnodes.live',
'https://apinode4.qortalnodes.live',
];
async function findUsableApi() {
for (const endpoint of apiEndpoints) {
try {
const response = await fetch(`${endpoint}/admin/status`);
if (!response.ok) throw new Error("Failed to fetch");
const data = await response.json();
if (data.isSynchronizing === false && data.syncPercent === 100) {
console.log(`Usable API found: ${endpoint}`);
return endpoint;
} else {
console.log(`API not ready: ${endpoint}`);
}
} catch (error) {
console.error(`Error checking API ${endpoint}:`, error);
for (const endpoint of apiEndpoints) {
try {
const response = await fetch(`${endpoint}/admin/status`);
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
if (data.isSynchronizing === false && data.syncPercent === 100) {
console.log(`Usable API found: ${endpoint}`);
return endpoint;
} else {
console.log(`API not ready: ${endpoint}`);
}
} catch (error) {
console.error(`Error checking API ${endpoint}:`, error);
}
throw new Error("No usable API found");
}
throw new Error('No usable API found');
}
async function getSaveWallet() {
const res = await getData<any>("walletInfo").catch(() => null);
const res = await getData<any>('walletInfo').catch(() => null);
if (res) {
return res
} else {
throw new Error("No wallet saved");
}
}
export async function getNameInfo() {
const wallet = await getSaveWallet();
const address = wallet.address0;
const validApi = await getBaseApi()
const response = await fetch(validApi + "/names/address/" + address);
const nameData = await response.json();
if (nameData?.length > 0) {
return nameData[0].name;
} else {
return "";
}
}
async function getKeyPair() {
const res = await getData<any>("keyPair").catch(() => null);
if (res) {
return res
} else {
throw new Error("Wallet not authenticated");
}
return res;
} else {
throw new Error('No wallet saved');
}
const getPublicKeys = async (groupNumber: number) => {
const validApi = await getBaseApi();
const response = await fetch(`${validApi}/groups/members/${groupNumber}?limit=0`);
const groupData = await response.json();
if (groupData && Array.isArray(groupData.members)) {
// Use the request queue for fetching public keys
const memberPromises = groupData.members
.filter((member) => member.member)
.map((member) =>
requestQueueGetPublicKeys.enqueue(async () => {
const resAddress = await fetch(`${validApi}/addresses/${member.member}`);
const resData = await resAddress.json();
return resData.publicKey;
})
);
const members = await Promise.all(memberPromises);
return members;
}
return [];
};
}
export const getPublicKeysByAddress = async (admins: string[]) => {
const validApi = await getBaseApi();
if (Array.isArray(admins)) {
// Use the request queue to limit concurrent fetches
const memberPromises = admins
.filter((address) => address) // Ensure the address is valid
.map((address) =>
requestQueueGetPublicKeys.enqueue(async () => {
const resAddress = await fetch(`${validApi}/addresses/${address}`);
const resData = await resAddress.json();
return resData.publicKey;
})
);
const members = await Promise.all(memberPromises);
return members;
}
return []; // Return empty array if admins is not an array
};
export async function getNameInfo() {
const wallet = await getSaveWallet();
const address = wallet.address0;
const validApi = await getBaseApi();
const response = await fetch(validApi + '/names/address/' + address);
const nameData = await response.json();
if (nameData?.length > 0) {
return nameData[0].name;
} else {
return '';
}
}
async function getKeyPair() {
const res = await getData<any>('keyPair').catch(() => null);
if (res) {
return res;
} else {
throw new Error('Wallet not authenticated');
}
}
const getPublicKeys = async (groupNumber: number) => {
const validApi = await getBaseApi();
const response = await fetch(
`${validApi}/groups/members/${groupNumber}?limit=0`
);
const groupData = await response.json();
export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousData}: {
groupId: number,
previousData: Object,
}) => {
try {
let highestKey = 0
if(previousData){
highestKey = Math.max(...Object.keys((previousData || {})).filter(item=> !isNaN(+item)).map(Number));
}
const resKeyPair = await getKeyPair()
const parsedData = resKeyPair
const privateKey = parsedData.privateKey
const userPublicKey = parsedData.publicKey
const groupmemberPublicKeys = await getPublicKeys(groupId)
const symmetricKey = createSymmetricKeyAndNonce()
const nextNumber = highestKey + 1
const objectToSave = {
...previousData,
[nextNumber]: symmetricKey
}
const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave)
const encryptedData = encryptDataGroup({
data64: symmetricKeyAndNonceBase64,
publicKeys: groupmemberPublicKeys,
privateKey,
userPublicKey
if (groupData && Array.isArray(groupData.members)) {
// Use the request queue for fetching public keys
const memberPromises = groupData.members
.filter((member) => member.member)
.map((member) =>
requestQueueGetPublicKeys.enqueue(async () => {
const resAddress = await fetch(
`${validApi}/addresses/${member.member}`
);
const resData = await resAddress.json();
return resData.publicKey;
})
if(encryptedData){
const registeredName = await getNameInfo()
const data = await publishData({
registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
})
return {
data,
numberOfMembers: groupmemberPublicKeys.length
}
} else {
throw new Error('Cannot encrypt content')
}
} catch (error: any) {
throw new Error(error.message);
);
const members = await Promise.all(memberPromises);
return members;
}
return [];
};
export const getPublicKeysByAddress = async (admins: string[]) => {
const validApi = await getBaseApi();
if (Array.isArray(admins)) {
// Use the request queue to limit concurrent fetches
const memberPromises = admins
.filter((address) => address) // Ensure the address is valid
.map((address) =>
requestQueueGetPublicKeys.enqueue(async () => {
const resAddress = await fetch(`${validApi}/addresses/${address}`);
const resData = await resAddress.json();
return resData.publicKey;
})
);
const members = await Promise.all(memberPromises);
return members;
}
return []; // Return empty array if admins is not an array
};
export const encryptAndPublishSymmetricKeyGroupChat = async ({
groupId,
previousData,
}: {
groupId: number;
previousData: Object;
}) => {
try {
let highestKey = 0;
if (previousData) {
highestKey = Math.max(
...Object.keys(previousData || {})
.filter((item) => !isNaN(+item))
.map(Number)
);
}
}
export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({groupId, previousData, admins}: {
groupId: number,
previousData: Object,
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const privateKey = parsedData.privateKey;
const userPublicKey = parsedData.publicKey;
const groupmemberPublicKeys = await getPublicKeys(groupId);
const symmetricKey = createSymmetricKeyAndNonce();
const nextNumber = highestKey + 1;
const objectToSave = {
...previousData,
[nextNumber]: symmetricKey,
};
const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave);
const encryptedData = encryptDataGroup({
data64: symmetricKeyAndNonceBase64,
publicKeys: groupmemberPublicKeys,
privateKey,
userPublicKey,
});
if (encryptedData) {
const registeredName = await getNameInfo();
const data = await publishData({
registeredName,
file: encryptedData,
service: 'DOCUMENT_PRIVATE',
identifier: `symmetric-qchat-group-${groupId}`,
uploadType: 'file',
isBase64: true,
withFee: true,
});
return {
data,
numberOfMembers: groupmemberPublicKeys.length,
};
} else {
throw new Error('Cannot encrypt content');
}
} catch (error: any) {
throw new Error(error.message);
}
};
export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({
groupId,
previousData,
admins,
}: {
groupId: number;
previousData: Object;
}) => {
try {
let highestKey = 0
if(previousData){
highestKey = Math.max(...Object.keys((previousData || {})).filter(item=> !isNaN(+item)).map(Number));
}
const resKeyPair = await getKeyPair()
const parsedData = resKeyPair
const privateKey = parsedData.privateKey
const userPublicKey = parsedData.publicKey
const groupmemberPublicKeys = await getPublicKeysByAddress(admins.map((admin)=> admin.address))
let highestKey = 0;
if (previousData) {
highestKey = Math.max(
...Object.keys(previousData || {})
.filter((item) => !isNaN(+item))
.map(Number)
);
}
const symmetricKey = createSymmetricKeyAndNonce()
const nextNumber = highestKey + 1
const objectToSave = {
...previousData,
[nextNumber]: symmetricKey
}
const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave)
const encryptedData = encryptDataGroup({
data64: symmetricKeyAndNonceBase64,
publicKeys: groupmemberPublicKeys,
privateKey,
userPublicKey
})
if(encryptedData){
const registeredName = await getNameInfo()
const data = await publishData({
registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
})
return {
data,
numberOfMembers: groupmemberPublicKeys.length
}
} else {
throw new Error('Cannot encrypt content')
}
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const privateKey = parsedData.privateKey;
const userPublicKey = parsedData.publicKey;
const groupmemberPublicKeys = await getPublicKeysByAddress(
admins.map((admin) => admin.address)
);
const symmetricKey = createSymmetricKeyAndNonce();
const nextNumber = highestKey + 1;
const objectToSave = {
...previousData,
[nextNumber]: symmetricKey,
};
const symmetricKeyAndNonceBase64 = await objectToBase64(objectToSave);
const encryptedData = encryptDataGroup({
data64: symmetricKeyAndNonceBase64,
publicKeys: groupmemberPublicKeys,
privateKey,
userPublicKey,
});
if (encryptedData) {
const registeredName = await getNameInfo();
const data = await publishData({
registeredName,
file: encryptedData,
service: 'DOCUMENT_PRIVATE',
identifier: `admins-symmetric-qchat-group-${groupId}`,
uploadType: 'file',
isBase64: true,
withFee: true,
});
return {
data,
numberOfMembers: groupmemberPublicKeys.length,
};
} else {
throw new Error('Cannot encrypt content');
}
} catch (error: any) {
throw new Error(error.message);
throw new Error(error.message);
}
}
export const publishGroupEncryptedResource = async ({encryptedData, identifier}) => {
try {
if(encryptedData && identifier){
const registeredName = await getNameInfo()
if(!registeredName) throw new Error('You need a name to publish')
const data = await publishData({
registeredName, file: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'file', isBase64: true, withFee: true
})
return data
} else {
throw new Error('Cannot encrypt content')
}
} catch (error: any) {
throw new Error(error.message);
}
}
export const publishOnQDN = async ({data, identifier, service, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType = 'file'
};
export const publishGroupEncryptedResource = async ({
encryptedData,
identifier,
}) => {
try {
if (encryptedData && identifier) {
const registeredName = await getNameInfo();
if (!registeredName) throw new Error('You need a name to publish');
const data = await publishData({
registeredName,
file: encryptedData,
service: 'DOCUMENT',
identifier,
uploadType: 'file',
isBase64: true,
withFee: true,
});
return data;
} else {
throw new Error('Cannot encrypt content');
}
} catch (error: any) {
throw new Error(error.message);
}
};
if(data && service){
const registeredName = await getNameInfo()
if(!registeredName) throw new Error('You need a name to publish')
const res = await publishData({
registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5
})
return res
export const publishOnQDN = async ({
data,
identifier,
service,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType = 'file',
}) => {
if (data && service) {
const registeredName = await getNameInfo();
if (!registeredName) throw new Error('You need a name to publish');
} else {
throw new Error('Cannot publish content')
}
}
const res = await publishData({
registeredName,
file: data,
service,
identifier,
uploadType,
isBase64: true,
withFee: true,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
});
return res;
} else {
throw new Error('Cannot publish content');
}
};
export function uint8ArrayToBase64(uint8Array: any) {
const length = uint8Array.length
let binaryString = ''
const chunkSize = 1024 * 1024; // Process 1MB at a time
for (let i = 0; i < length; i += chunkSize) {
const chunkEnd = Math.min(i + chunkSize, length)
const chunk = uint8Array.subarray(i, chunkEnd)
const length = uint8Array.length;
let binaryString = '';
const chunkSize = 1024 * 1024; // Process 1MB at a time
for (let i = 0; i < length; i += chunkSize) {
const chunkEnd = Math.min(i + chunkSize, length);
const chunk = uint8Array.subarray(i, chunkEnd);
// @ts-ignore
binaryString += Array.from(chunk, byte => String.fromCharCode(byte)).join('')
}
return btoa(binaryString)
// @ts-ignore
binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(
''
);
}
return btoa(binaryString);
}
export function base64ToUint8Array(base64: string) {
const binaryString = atob(base64)
const len = binaryString.length
const bytes = new Uint8Array(len)
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
export const decryptGroupEncryption = async ({data}: {
data: string
}) => {
try {
const resKeyPair = await getKeyPair()
const parsedData = resKeyPair
const privateKey = parsedData.privateKey
const encryptedData = decryptGroupData(
data,
privateKey,
)
return {
data: uint8ArrayToBase64(encryptedData.decryptedData),
count: encryptedData.count
}
} catch (error: any) {
throw new Error(error.message);
}
return bytes;
}
export const decryptGroupEncryption = async ({ data }: { data: string }) => {
try {
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const privateKey = parsedData.privateKey;
const encryptedData = decryptGroupData(data, privateKey);
return {
data: uint8ArrayToBase64(encryptedData.decryptedData),
count: encryptedData.count,
};
} catch (error: any) {
throw new Error(error.message);
}
};
export function uint8ArrayToObject(uint8Array: any) {
// Decode the byte array using TextDecoder
const decoder = new TextDecoder()
const jsonString = decoder.decode(uint8Array)
// Convert the JSON string back into an object
return JSON.parse(jsonString)
}
// Decode the byte array using TextDecoder
const decoder = new TextDecoder();
const jsonString = decoder.decode(uint8Array);
// Convert the JSON string back into an object
return JSON.parse(jsonString);
}

View File

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

View File

@@ -1,7 +1,19 @@
import React from 'react'
import './customloader.css'
import React from 'react';
import './customloader.css';
import { Box, useTheme } from '@mui/material';
export const CustomLoader = () => {
const theme = useTheme();
return (
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
)
}
<Box
sx={{
'--text-primary': theme.palette.text.primary,
}}
className="lds-ellipsis"
>
<div></div>
<div></div>
<div></div>
<div></div>
</Box>
);
};

View File

@@ -1,28 +1,29 @@
import React, { useState, useEffect, useRef } from 'react'
import { useInView } from 'react-intersection-observer'
import CircularProgress from '@mui/material/CircularProgress'
import React, { useState, useEffect, useRef } from 'react';
import { useInView } from 'react-intersection-observer';
import CircularProgress from '@mui/material/CircularProgress';
import { useTheme } from '@mui/material';
interface Props {
onLoadMore: () => Promise<void>
onLoadMore: () => Promise<void>;
}
const LazyLoad: React.FC<Props> = ({ onLoadMore }) => {
const [isFetching, setIsFetching] = useState<boolean>(false)
const firstLoad = useRef(false)
const [isFetching, setIsFetching] = useState<boolean>(false);
const theme = useTheme();
const firstLoad = useRef(false);
const [ref, inView] = useInView({
threshold: 0.7
})
threshold: 0.7,
});
useEffect(() => {
if (inView) {
setIsFetching(true)
setIsFetching(true);
onLoadMore().finally(() => {
setIsFetching(false)
firstLoad.current = true
})
setIsFetching(false);
firstLoad.current = true;
});
}
}, [inView])
}, [inView]);
return (
<div
@@ -30,18 +31,22 @@ const LazyLoad: React.FC<Props> = ({ onLoadMore }) => {
style={{
display: 'flex',
justifyContent: 'center',
minHeight: '25px'
minHeight: '25px',
}}
>
<div
style={{
visibility: isFetching ? 'visible' : 'hidden'
visibility: isFetching ? 'visible' : 'hidden',
}}
>
<CircularProgress />
<CircularProgress
sx={{
color: theme.palette.text.primary,
}}
/>
</div>
</div>
)
}
);
};
export default LazyLoad
export default LazyLoad;

View File

@@ -1,15 +1,15 @@
import { Box } from "@mui/material";
import { Box } from '@mui/material';
export const Spacer = ({ height, width, ...props }: any) => {
return (
<Box
sx={{
height: height ? height : '0px',
display: 'flex',
flexShrink: 0,
width: width ? width : '0px',
...(props || {})
}}
/>
);
};
return (
<Box
sx={{
height: height ? height : '0px',
display: 'flex',
flexShrink: 0,
width: width ? width : '0px',
...(props || {}),
}}
/>
);
};

View File

@@ -1,19 +1,27 @@
/* HTML: <div class="loader"></div> */
.loader-bar {
width: 45px;
aspect-ratio: .75;
--c:no-repeat linear-gradient(currentColor 0 0);
background:
var(--c) 0% 100%,
var(--c) 50% 100%,
var(--c) 100% 100%;
background-size: 20% 65%;
animation: l8 1s infinite linear;
width: 45px;
aspect-ratio: 0.75;
--c: no-repeat linear-gradient(currentColor 0 0);
background: var(--c) 0% 100%, var(--c) 50% 100%, var(--c) 100% 100%;
background-size: 20% 65%;
animation: l8 1s infinite linear;
}
@keyframes l8 {
16.67% {
background-position: 0% 0%, 50% 100%, 100% 100%;
}
@keyframes l8 {
16.67% {background-position: 0% 0% ,50% 100%,100% 100%}
33.33% {background-position: 0% 0% ,50% 0% ,100% 100%}
50% {background-position: 0% 0% ,50% 0% ,100% 0% }
66.67% {background-position: 0% 100%,50% 0% ,100% 0% }
83.33% {background-position: 0% 100%,50% 100%,100% 0% }
}
33.33% {
background-position: 0% 0%, 50% 0%, 100% 100%;
}
50% {
background-position: 0% 0%, 50% 0%, 100% 0%;
}
66.67% {
background-position: 0% 100%, 50% 0%, 100% 0%;
}
83.33% {
background-position: 0% 100%, 50% 100%, 100% 0%;
}
}

View File

@@ -1,7 +1,6 @@
.lds-ellipsis {
color: white
}
color: var(--text-primary);
}
.lds-ellipsis,
.lds-ellipsis div {
box-sizing: border-box;
@@ -61,4 +60,3 @@
transform: translate(24px, 0);
}
}

View File

@@ -1,168 +1,167 @@
import React, { useCallback, useRef } from 'react';
import { useRecoilState } from 'recoil';
import { resourceDownloadControllerAtom } from '../atoms/global';
import { getBaseApiReact } from '../App';
import { useSetAtom } from 'jotai';
export const useFetchResources = () => {
const [resources, setResources] = useRecoilState(resourceDownloadControllerAtom);
const setResources = useSetAtom(resourceDownloadControllerAtom);
const downloadResource = useCallback(({ service, name, identifier }, build) => {
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
service,
name,
identifier,
},
}));
const downloadResource = useCallback(
({ service, name, identifier }, build) => {
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
service,
name,
identifier,
},
}));
try {
let isCalling = false;
let percentLoaded = 0;
let timer = 24;
let tries = 0;
let calledFirstTime = false
let intervalId
let timeoutId
const callFunction = async ()=> {
if (isCalling) return;
isCalling = true;
try {
let isCalling = false;
let percentLoaded = 0;
let timer = 24;
let tries = 0;
let calledFirstTime = false;
let intervalId;
let timeoutId;
const callFunction = async () => {
if (isCalling) return;
isCalling = true;
let res
if(!build){
let res;
if (!build) {
const urlFirstTime = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}`;
const resCall = await fetch(urlFirstTime, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
res = await resCall.json()
if(tries > 18 ){
if(intervalId){
clearInterval(intervalId)
}
if(timeoutId){
clearTimeout(timeoutId)
}
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: {
...res,
status: 'FAILED_TO_DOWNLOAD',
},
},
}));
return
}
tries = tries + 1
}
if(build || (calledFirstTime === false && res?.status !== 'READY')){
const url = `${getBaseApiReact()}/arbitrary/resource/properties/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
res = await resCall.json();
}
calledFirstTime = true
isCalling = false;
if (res.localChunkCount) {
if (res.percentLoaded) {
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
timer = 24;
isCalling = true;
// Update Recoil state for refetching
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
res = await resCall.json();
if (tries > 18) {
if (intervalId) {
clearInterval(intervalId);
}
if (timeoutId) {
clearTimeout(timeoutId);
}
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: {
...res,
status: 'REFETCHING',
status: 'FAILED_TO_DOWNLOAD',
},
},
}));
timeoutId = setTimeout(() => {
isCalling = false;
downloadResource({ name, service, identifier }, true);
}, 25000);
return;
}
percentLoaded = res.percentLoaded;
tries = tries + 1;
}
// Update Recoil state for progress
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
// Check if progress is 100% and clear interval if true
if (res?.status === 'READY') {
if(intervalId){
clearInterval(intervalId);
}
if(timeoutId){
clearTimeout(timeoutId)
}
// Update Recoil state for completion
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
if(res?.status === 'DOWNLOADED'){
const url = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: "GET",
if (build || (calledFirstTime === false && res?.status !== 'READY')) {
const url = `${getBaseApiReact()}/arbitrary/resource/properties/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
res = await resCall.json();
}
}
callFunction()
intervalId = setInterval(async () => {
callFunction()
}, 5000);
} catch (error) {
console.error('Error during resource fetch:', error);
}
}, [setResources]);
res = await resCall.json();
}
calledFirstTime = true;
isCalling = false;
return { downloadResource };
if (res.localChunkCount) {
if (res.percentLoaded) {
if (
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
timer = 24;
isCalling = true;
// Update Recoil state for refetching
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: {
...res,
status: 'REFETCHING',
},
},
}));
timeoutId = setTimeout(() => {
isCalling = false;
downloadResource({ name, service, identifier }, true);
}, 25000);
return;
}
percentLoaded = res.percentLoaded;
}
// Update Recoil state for progress
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
// Check if progress is 100% and clear interval if true
if (res?.status === 'READY') {
if (intervalId) {
clearInterval(intervalId);
}
if (timeoutId) {
clearTimeout(timeoutId);
}
// Update Recoil state for completion
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
if (res?.status === 'DOWNLOADED') {
const url = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
res = await resCall.json();
}
};
callFunction();
intervalId = setInterval(async () => {
callFunction();
}, 5000);
} catch (error) {
console.error('Error during resource fetch:', error);
}
},
[setResources]
);
return downloadResource;
};

View File

@@ -1,64 +1,50 @@
import { useRef, useState } from 'react';
import { useRef, useState, useCallback, useMemo } from 'react';
interface State {
isShow: boolean;
isShow: boolean;
}
export const useModal = () => {
const [state, setState] = useState<State>({
isShow: false,
const [state, setState] = useState<State>({ isShow: false });
const [message, setMessage] = useState({ publishFee: '', message: '' });
const promiseConfig = useRef<any>(null);
const show = useCallback((data) => {
setMessage(data);
return new Promise((resolve, reject) => {
promiseConfig.current = { resolve, reject };
setState({ isShow: true });
});
const [message, setMessage] = useState({
publishFee: "",
message: ""
});
const promiseConfig = useRef<any>(null);
const show = async (data) => {
setMessage(data)
return new Promise((resolve, reject) => {
promiseConfig.current = {
resolve,
reject,
};
setState({
isShow: true,
});
});
};
}, []);
const hide = () => {
setState({
isShow: false,
});
setMessage({
publishFee: "",
message: ""
})
};
const hide = useCallback(() => {
setState({ isShow: false });
setMessage({ publishFee: '', message: '' });
}, []);
const onOk = (payload:any) => {
const { resolve } = promiseConfig.current;
setMessage({
publishFee: "",
message: ""
})
hide();
resolve(payload);
};
const onOk = useCallback(
(payload: any) => {
const { resolve } = promiseConfig.current || {};
hide();
resolve?.(payload);
},
[hide]
);
const onCancel = () => {
const { reject } = promiseConfig.current;
hide();
reject();
setMessage({
publishFee: "",
message: ""
})
};
return {
show,
onOk,
onCancel,
isShow: state.isShow,
message
};
};
const onCancel = useCallback(() => {
const { reject } = promiseConfig.current || {};
hide();
reject?.();
}, [hide]);
return useMemo(
() => ({
show,
onOk,
onCancel,
isShow: state.isShow,
message,
}),
[show, onOk, onCancel, state.isShow, message]
);
};

View File

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

View File

@@ -1,15 +1,13 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppDownloadButton,
AppDownloadButtonText,
AppInfoAppName,
AppInfoSnippetContainer,
AppInfoSnippetLeft,
AppInfoSnippetMiddle,
AppInfoSnippetRight,
AppInfoUserName,
AppsCategoryInfo,
AppsCategoryInfoLabel,
@@ -17,193 +15,228 @@ import {
AppsCategoryInfoValue,
AppsInfoDescription,
AppsLibraryContainer,
AppsParent,
AppsWidthLimiter,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
} from './Apps-styles';
import { Avatar, Box, useTheme } from '@mui/material';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating';
import {
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop';
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
import { AppRating } from "./AppRating";
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global";
import { saveToLocalStorage } from "./AppsNavBar";
import { useRecoilState, useSetRecoilState } from "recoil";
import { useAtom, useSetAtom } from 'jotai';
export const AppInfo = ({ app, myName }) => {
const isInstalled = app?.status?.status === "READY";
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom
);
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service)
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
const theme = useTheme();
const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service
);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
return (
<AppsLibraryContainer
sx={{
height: !isMobile && "100%",
justifyContent: !isMobile && "flex-start",
alignItems: isMobile && 'center'
height: '100%',
justifyContent: 'flex-start',
}}
>
<Box sx={{
display: 'flex',
flexDirection: 'column',
maxWidth: "500px",
width: '90%'
}}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
maxWidth: '500px',
width: '90%',
}}
>
<Spacer height="30px" />
{!isMobile && <Spacer height="30px" />}
<AppsWidthLimiter>
<AppInfoSnippetContainer>
<AppInfoSnippetLeft
sx={{
flexGrow: 1,
gap: "18px",
}}
>
<AppCircleContainer
<AppsWidthLimiter>
<AppInfoSnippetContainer>
<AppInfoSnippetLeft
sx={{
width: "auto",
flexGrow: 1,
gap: '18px',
}}
>
<AppCircle
<AppCircleContainer
sx={{
border: "none",
height: "100px",
width: "100px",
width: 'auto',
}}
>
<Avatar
<AppCircle
sx={{
height: "43px",
width: "43px",
"& img": {
objectFit: "fill",
},
border: 'none',
height: '100px',
width: '100px',
}}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "43px",
height: "auto",
<Avatar
sx={{
height: '43px',
width: '43px',
'& img': {
objectFit: 'fill',
},
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
<AppInfoSnippetMiddle>
<AppInfoAppName>
{app?.metadata?.title || app?.name}
</AppInfoAppName>
<Spacer height="6px" />
<AppInfoUserName>{app?.name}</AppInfoUserName>
<Spacer height="3px" />
</AppInfoSnippetMiddle>
</AppInfoSnippetLeft>
<AppInfoSnippetRight></AppInfoSnippetRight>
</AppInfoSnippetContainer>
<Spacer height="11px" />
<Box sx={{
width: '100%',
display: 'flex',
alignItems: 'center',
gap: '20px'
}}>
<AppDownloadButton
onClick={() => {
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) => !(item?.name === app?.name && item?.service === app?.service)
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: '43px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
<AppInfoSnippetMiddle>
<AppInfoAppName>
{app?.metadata?.title || app?.name}
</AppInfoAppName>
<Spacer height="6px" />
<AppInfoUserName>{app?.name}</AppInfoUserName>
<Spacer height="3px" />
</AppInfoSnippetMiddle>
</AppInfoSnippetLeft>
</AppInfoSnippetContainer>
<Spacer height="11px" />
<Box
sx={{
width: '100%',
display: 'flex',
alignItems: 'center',
gap: '20px',
}}
>
<AppDownloadButton
onClick={() => {
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) =>
!(
item?.name === app?.name &&
item?.service === app?.service
)
);
} else {
// Add the selected app if it is not pinned
updatedApps = [
...prev,
{
name: app?.name,
service: app?.service,
},
];
}
saveToLocalStorage(
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
);
} else {
// Add the selected app if it is not pinned
updatedApps = [...prev, {
name: app?.name,
service: app?.service,
}];
}
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps)
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now())
}}
sx={{
backgroundColor: "#359ff7ff",
width: "100%",
maxWidth: "320px",
height: "29px",
opacity: isSelectedAppPinned ? 0.6 : 1
}}
>
<AppDownloadButtonText>
{!isMobile ? (
<>
{isSelectedAppPinned ? 'Unpin from dashboard' : 'Pin to dashboard'}
</>
) : (
<>
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
</>
)}
</AppDownloadButtonText>
</AppDownloadButton>
<AppDownloadButton
onClick={() => {
executeEvent("addTab", {
data: app,
});
}}
sx={{
backgroundColor: isInstalled ? "#0091E1" : "#247C0E",
width: "100%",
maxWidth: "320px",
height: "29px",
}}
>
<AppDownloadButtonText>
{isInstalled ? "Open" : "Download"}
</AppDownloadButtonText>
</AppDownloadButton>
</Box>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsWidthLimiter>
<AppsCategoryInfo>
<AppRating ratingCountPosition="top" myName={myName} app={app} />
<Spacer width="16px" />
<Spacer height="40px" width="1px" backgroundColor="white" />
<Spacer width="16px" />
<AppsCategoryInfoSub>
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
<Spacer height="4px" />
<AppsCategoryInfoValue>
{app?.metadata?.categoryName || "none"}
</AppsCategoryInfoValue>
</AppsCategoryInfoSub>
</AppsCategoryInfo>
<Spacer height="30px" />
<AppInfoAppName>About this Q-App</AppInfoAppName>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsInfoDescription>
{app?.metadata?.description || "No description"}
</AppsInfoDescription>
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
}}
sx={{
backgroundColor: theme.palette.background.paper,
height: '29px',
maxWidth: '320px',
opacity: isSelectedAppPinned ? 0.6 : 1,
width: '100%',
}}
>
<AppDownloadButtonText>
{isSelectedAppPinned
? 'Unpin from dashboard'
: 'Pin to dashboard'}
</AppDownloadButtonText>
</AppDownloadButton>
<AppDownloadButton
onClick={() => {
executeEvent('addTab', {
data: app,
});
}}
sx={{
backgroundColor: isInstalled
? theme.palette.primary.main
: theme.palette.background.paper,
height: '29px',
maxWidth: '320px',
width: '100%',
}}
>
<AppDownloadButtonText>
{isInstalled ? 'Open' : 'Download'}
</AppDownloadButtonText>
</AppDownloadButton>
</Box>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsWidthLimiter>
<AppsCategoryInfo>
<AppRating ratingCountPosition="top" myName={myName} app={app} />
<Spacer width="16px" />
<Spacer
backgroundColor={theme.palette.background.paper}
height="40px"
width="1px"
/>
<Spacer width="16px" />
<AppsCategoryInfoSub>
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
<Spacer height="4px" />
<AppsCategoryInfoValue>
{app?.metadata?.categoryName || 'none'}
</AppsCategoryInfoValue>
</AppsCategoryInfoSub>
</AppsCategoryInfo>
<Spacer height="30px" />
<AppInfoAppName>About this Q-App</AppInfoAppName>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsInfoDescription>
{app?.metadata?.description || 'No description'}
</AppsInfoDescription>
</Box>
</AppsLibraryContainer>
);

View File

@@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
import {
AppCircle,
AppCircleContainer,
@@ -10,148 +10,185 @@ import {
AppInfoSnippetMiddle,
AppInfoSnippetRight,
AppInfoUserName,
} from "./Apps-styles";
import { Avatar, ButtonBase } from "@mui/material";
import { getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
} from './Apps-styles';
import { Avatar, ButtonBase, useTheme } from '@mui/material';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating';
import {
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop';
import { useAtom, useSetAtom } from 'jotai';
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
import { AppRating } from "./AppRating";
import { useRecoilState, useSetRecoilState } from "recoil";
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global";
import { saveToLocalStorage } from "./AppsNavBar";
export const AppInfoSnippet = ({
app,
myName,
isFromCategory,
parentStyles = {},
}) => {
const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom
);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
export const AppInfoSnippet = ({ app, myName, isFromCategory, parentStyles = {} }) => {
const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service
);
const isInstalled = app?.status?.status === 'READY'
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
const theme = useTheme();
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service)
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
return (
<AppInfoSnippetContainer sx={{
...parentStyles
}}>
<AppInfoSnippetContainer
sx={{
...parentStyles,
}}
>
<AppInfoSnippetLeft>
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
onClick={()=> {
if(isFromCategory){
executeEvent("selectedAppInfoCategory", {
<ButtonBase
sx={{
height: '80px',
width: '60px',
}}
onClick={() => {
if (isFromCategory) {
executeEvent('selectedAppInfoCategory', {
data: app,
});
return;
}
executeEvent('selectedAppInfo', {
data: app,
});
return
}
executeEvent("selectedAppInfo", {
data: app,
});
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: 'none',
}}
>
<Avatar
sx={{
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: '31px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
</ButtonBase>
<AppInfoSnippetMiddle>
<ButtonBase
onClick={() => {
if (isFromCategory) {
executeEvent('selectedAppInfoCategory', {
data: app,
});
return;
}
executeEvent('selectedAppInfo', {
data: app,
});
}}
>
<Avatar
sx={{
height: "42px",
width: "42px",
'& img': {
objectFit: 'fill',
}
}}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
</ButtonBase>
<AppInfoSnippetMiddle>
<ButtonBase onClick={()=> {
if(isFromCategory){
executeEvent("selectedAppInfoCategory", {
<AppInfoAppName>{app?.metadata?.title || app?.name}</AppInfoAppName>
</ButtonBase>
<Spacer height="6px" />
<AppInfoUserName>{app?.name}</AppInfoUserName>
<Spacer height="3px" />
<AppRating app={app} myName={myName} />
</AppInfoSnippetMiddle>
</AppInfoSnippetLeft>
<AppInfoSnippetRight
sx={{
gap: '10px',
}}
>
<AppDownloadButton
onClick={() => {
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) =>
!(
item?.name === app?.name && item?.service === app?.service
)
);
} else {
// Add the selected app if it is not pinned
updatedApps = [
...prev,
{
name: app?.name,
service: app?.service,
},
];
}
saveToLocalStorage(
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
);
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
}}
sx={{
backgroundColor: theme.palette.background.paper,
opacity: isSelectedAppPinned ? 0.6 : 1,
}}
>
<AppDownloadButtonText>
{' '}
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
</AppDownloadButtonText>
</AppDownloadButton>
<AppDownloadButton
onClick={() => {
executeEvent('addTab', {
data: app,
});
return
}
executeEvent("selectedAppInfo", {
data: app,
});
}}>
<AppInfoAppName >
{app?.metadata?.title || app?.name}
</AppInfoAppName>
</ButtonBase>
<Spacer height="6px" />
<AppInfoUserName>
{ app?.name}
</AppInfoUserName>
<Spacer height="3px" />
<AppRating app={app} myName={myName} />
</AppInfoSnippetMiddle>
</AppInfoSnippetLeft>
<AppInfoSnippetRight sx={{
gap: '10px'
}}>
{!isMobile && (
<AppDownloadButton onClick={()=> {
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) => !(item?.name === app?.name && item?.service === app?.service)
);
} else {
// Add the selected app if it is not pinned
updatedApps = [...prev, {
name: app?.name,
service: app?.service,
}];
}
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps)
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now())
}} sx={{
backgroundColor: '#359ff7ff',
opacity: isSelectedAppPinned ? 0.6 : 1
}}>
<AppDownloadButtonText> {isSelectedAppPinned ? 'Unpin' : 'Pin'}</AppDownloadButtonText>
</AppDownloadButton>
)}
<AppDownloadButton onClick={()=> {
executeEvent("addTab", {
data: app
})
}} sx={{
backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
}}>
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText>
}}
sx={{
backgroundColor: isInstalled
? theme.palette.primary.main
: theme.palette.background.paper,
}}
>
<AppDownloadButtonText>
{isInstalled ? 'Open' : 'Download'}
</AppDownloadButtonText>
</AppDownloadButton>
</AppInfoSnippetRight>
</AppInfoSnippetContainer>

View File

@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@@ -19,90 +19,80 @@ import {
PublishQAppCTAButton,
PublishQAppChoseFile,
PublishQAppInfo,
} from "./Apps-styles";
} from './Apps-styles';
import {
Avatar,
Box,
ButtonBase,
InputBase,
InputLabel,
MenuItem,
Select,
} from "@mui/material";
import {
Select as BaseSelect,
SelectProps,
selectClasses,
SelectRootSlotProps,
} from "@mui/base/Select";
import { Option as BaseOption, optionClasses } from "@mui/base/Option";
import { styled } from "@mui/system";
import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
import { useDropzone } from "react-dropzone";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { getFee } from "../../background";
import { fileToBase64 } from "../../utils/fileReading";
useTheme,
} from '@mui/material';
import { styled } from '@mui/system';
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { useDropzone } from 'react-dropzone';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getFee } from '../../background';
import { fileToBase64 } from '../../utils/fileReading';
const CustomSelect = styled(Select)({
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
"& .MuiSelect-select": {
padding: "0px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
'& .MuiSelect-select': {
padding: '0px',
},
"&:hover": {
borderColor: "none", // Border color on hover
'&:hover': {
borderColor: 'none', // Border color on hover
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: "none", // Border color when focused
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: 'none', // Border color when focused
},
"&.Mui-disabled": {
'&.Mui-disabled': {
opacity: 0.5, // Lower opacity when disabled
},
"& .MuiSvgIcon-root": {
color: "var(--50-white, #FFFFFF80)",
'& .MuiSvgIcon-root': {
color: 'var(--50-white, #FFFFFF80)',
},
});
const CustomMenuItem = styled(MenuItem)({
backgroundColor: "#1f1f1f", // Background for dropdown items
color: "#ccc",
"&:hover": {
backgroundColor: "#333", // Darker background on hover
},
// backgroundColor: '#1f1f1f', // Background for dropdown items
// color: '#ccc',
// '&:hover': {
// backgroundColor: '#333', // Darker background on hover
// },
});
export const AppPublish = ({ names, categories }) => {
const [name, setName] = useState("");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState("");
const [appType, setAppType] = useState("APP");
const [name, setName] = useState('');
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('');
const [appType, setAppType] = useState('APP');
const [file, setFile] = useState(null);
const { show } = useContext(MyContext);
const [tag1, setTag1] = useState("");
const [tag2, setTag2] = useState("");
const [tag3, setTag3] = useState("");
const [tag4, setTag4] = useState("");
const [tag5, setTag5] = useState("");
const theme = useTheme();
const [tag1, setTag1] = useState('');
const [tag2, setTag2] = useState('');
const [tag3, setTag3] = useState('');
const [tag4, setTag4] = useState('');
const [tag5, setTag5] = useState('');
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = useState("");
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const [isLoading, setIsLoading] = useState('');
const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const { getRootProps, getInputProps } = useDropzone({
accept: {
"application/zip": [".zip"], // Only accept zip files
'application/zip': ['.zip'], // Only accept zip files
},
maxSize: maxFileSize, // Set the max size based on appType
multiple: false, // Disable multiple file uploads
@@ -114,7 +104,7 @@ export const AppPublish = ({ names, categories }) => {
onDropRejected: (fileRejections) => {
fileRejections.forEach(({ file, errors }) => {
errors.forEach((error) => {
if (error.code === "file-too-large") {
if (error.code === 'file-too-large') {
console.error(
`File ${file.name} is too large. Max size allowed is ${
maxFileSize / (1024 * 1024)
@@ -128,13 +118,13 @@ export const AppPublish = ({ names, categories }) => {
const getQapp = React.useCallback(async (name, appType) => {
try {
setIsLoading("Loading app information");
setIsLoading('Loading app information');
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if (!response?.ok) return;
@@ -142,18 +132,18 @@ export const AppPublish = ({ names, categories }) => {
if (responseData?.length > 0) {
const myApp = responseData[0];
setTitle(myApp?.metadata?.title || "");
setDescription(myApp?.metadata?.description || "");
setCategory(myApp?.metadata?.category || "");
setTag1(myApp?.metadata?.tags[0] || "");
setTag2(myApp?.metadata?.tags[1] || "");
setTag3(myApp?.metadata?.tags[2] || "");
setTag4(myApp?.metadata?.tags[3] || "");
setTag5(myApp?.metadata?.tags[4] || "");
setTitle(myApp?.metadata?.title || '');
setDescription(myApp?.metadata?.description || '');
setCategory(myApp?.metadata?.category || '');
setTag1(myApp?.metadata?.tags[0] || '');
setTag2(myApp?.metadata?.tags[1] || '');
setTag3(myApp?.metadata?.tags[2] || '');
setTag4(myApp?.metadata?.tags[3] || '');
setTag5(myApp?.metadata?.tags[4] || '');
}
} catch (error) {
} finally {
setIsLoading("");
setIsLoading('');
}
}, []);
@@ -173,12 +163,12 @@ export const AppPublish = ({ names, categories }) => {
file,
};
const requiredFields = [
"name",
"title",
"description",
"category",
"appType",
"file",
'name',
'title',
'description',
'category',
'appType',
'file',
];
const missingFields: string[] = [];
@@ -188,32 +178,33 @@ export const AppPublish = ({ names, categories }) => {
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const fee = await getFee("ARBITRARY");
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to publish this app?",
publishFee: fee.fee + " QORT",
message: 'Would you like to publish this app?',
publishFee: fee.fee + ' QORT',
});
setIsLoading("Publishing... Please wait.");
setIsLoading('Publishing... Please wait.');
const fileBase64 = await fileToBase64(file);
await new Promise((res, rej) => {
window.sendMessage("publishOnQDN", {
data: fileBase64,
service: appType,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType: "zip",
})
window
.sendMessage('publishOnQDN', {
data: fileBase64,
service: appType,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType: 'zip',
})
.then((response) => {
if (!response?.error) {
res(response);
@@ -222,14 +213,13 @@ export const AppPublish = ({ names, categories }) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
setInfoSnack({
type: "success",
type: 'success',
message:
"Successfully published. Please wait a couple minutes for the network to propogate the changes.",
'Successfully published. Please wait a couple minutes for the network to propogate the changes.',
});
setOpenSnack(true);
const dataObj = {
@@ -242,35 +232,47 @@ export const AppPublish = ({ names, categories }) => {
},
created: Date.now(),
};
executeEvent("addTab", {
executeEvent('addTab', {
data: dataObj,
});
} catch (error) {
setInfoSnack({
type: "error",
message: error?.message || "Unable to publish app",
type: 'error',
message: error?.message || 'Unable to publish app',
});
setOpenSnack(true);
} finally {
setIsLoading("");
setIsLoading('');
}
};
return (
<AppsLibraryContainer sx={{
height: !isMobile ? '100%' : 'auto',
paddingTop: !isMobile && '30px',
alignItems: !isMobile && 'center'
}}>
<AppsWidthLimiter sx={{
width: !isMobile ? 'auto' : '90%'
}}>
<AppsLibraryContainer
sx={{
alignItems: 'center',
height: '100%',
paddingTop: '30px',
}}
>
<AppsWidthLimiter
sx={{
width: 'auto',
}}
>
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
<Spacer height="18px" />
<PublishQAppInfo>
Note: Currently, only one App and Website is allowed per Name.
</PublishQAppInfo>
<Spacer height="18px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Name/App</InputLabel>
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Name/App
</InputLabel>
<CustomSelect
placeholder="Select Name/App"
displayEmpty
@@ -280,19 +282,24 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: theme.palette.text.secondary,
}}
>
Select Name/App
</em>{" "}
</em>
{/* This is the placeholder item */}
</CustomMenuItem>
{names.map((name) => {
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
})}
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>App service type</InputLabel>
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
App service type
</InputLabel>
<CustomSelect
placeholder="SERVICE TYPE"
displayEmpty
@@ -302,59 +309,72 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: theme.palette.text.secondary,
}}
>
Select App Type
</em>{" "}
{/* This is the placeholder item */}
</em>
</CustomMenuItem>
<CustomMenuItem value={"APP"}>App</CustomMenuItem>
<CustomMenuItem value={"WEBSITE"}>Website</CustomMenuItem>
<CustomMenuItem value={'APP'}>App</CustomMenuItem>
<CustomMenuItem value={'WEBSITE'}>Website</CustomMenuItem>
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Title</InputLabel>
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Title
</InputLabel>
<InputBase
value={title}
onChange={(e) => setTitle(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
}}
placeholder="Title"
inputProps={{
"aria-label": "Title",
fontSize: "14px",
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Description</InputLabel>
<InputBase
value={description}
onChange={(e) => setDescription(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
}}
placeholder="Description"
inputProps={{
"aria-label": "Description",
fontSize: "14px",
'aria-label': 'Title',
fontSize: '14px',
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Category</InputLabel>
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Description
</InputLabel>
<InputBase
value={description}
onChange={(e) => setDescription(e.target.value)}
sx={{
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
}}
placeholder="Description"
inputProps={{
'aria-label': 'Description',
fontSize: '14px',
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Category
</InputLabel>
<CustomSelect
displayEmpty
placeholder="Select Category"
@@ -364,12 +384,11 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: theme.palette.text.secondary,
}}
>
Select Category
</em>{" "}
{/* This is the placeholder item */}
</em>
</CustomMenuItem>
{categories?.map((category) => {
return (
@@ -379,23 +398,28 @@ export const AppPublish = ({ names, categories }) => {
);
})}
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Tags</InputLabel>
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Tags
</InputLabel>
<AppPublishTagsContainer>
<InputBase
value={tag1}
onChange={(e) => setTag1(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 1"
inputProps={{
"aria-label": "Tag 1",
fontSize: "14px",
'aria-label': 'Tag 1',
fontSize: '14px',
fontWeight: 400,
}}
/>
@@ -403,16 +427,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag2}
onChange={(e) => setTag2(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 2"
inputProps={{
"aria-label": "Tag 2",
fontSize: "14px",
'aria-label': 'Tag 2',
fontSize: '14px',
fontWeight: 400,
}}
/>
@@ -420,16 +444,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag3}
onChange={(e) => setTag3(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 3"
inputProps={{
"aria-label": "Tag 3",
fontSize: "14px",
'aria-label': 'Tag 3',
fontSize: '14px',
fontWeight: 400,
}}
/>
@@ -437,16 +461,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag4}
onChange={(e) => setTag4(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 4"
inputProps={{
"aria-label": "Tag 4",
fontSize: "14px",
'aria-label': 'Tag 4',
fontSize: '14px',
fontWeight: 400,
}}
/>
@@ -454,27 +478,31 @@ export const AppPublish = ({ names, categories }) => {
value={tag5}
onChange={(e) => setTag5(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: `0.5px solid ${theme.palette.action.disabled}`,
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 5"
inputProps={{
"aria-label": "Tag 5",
fontSize: "14px",
'aria-label': 'Tag 5',
fontSize: '14px',
fontWeight: 400,
}}
/>
</AppPublishTagsContainer>
<Spacer height="30px" />
<PublishQAppInfo>
Select .zip file containing static content:{" "}
Select .zip file containing static content:{' '}
</PublishQAppInfo>
<Spacer height="10px" />
<PublishQAppInfo>{`(${
appType === "APP" ? "50mb" : "400mb"
appType === 'APP' ? '50mb' : '400mb'
} MB maximum)`}</PublishQAppInfo>
{file && (
<>
@@ -484,21 +512,25 @@ export const AppPublish = ({ names, categories }) => {
)}
<Spacer height="18px" />
<PublishQAppChoseFile {...getRootProps()}>
{" "}
{' '}
<input {...getInputProps()} />
Choose File
</PublishQAppChoseFile>
<Spacer height="35px" />
<PublishQAppCTAButton
sx={{
alignSelf: "center",
alignSelf: 'center',
}}
onClick={publishApp}
>
Publish
</PublishQAppCTAButton>
</AppsWidthLimiter>
<LoadingSnackbar
open={!!isLoading}
info={{
@@ -512,7 +544,6 @@ export const AppPublish = ({ names, categories }) => {
info={infoSnack}
setInfo={setInfoSnack}
/>
</AppsLibraryContainer>
);
};

View File

@@ -1,20 +1,14 @@
import { Box, Rating, Typography } from "@mui/material";
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { getFee } from "../../background";
import { MyContext, getBaseApiReact } from "../../App";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { StarFilledIcon } from "../../assets/svgs/StarFilled";
import { StarEmptyIcon } from "../../assets/svgs/StarEmpty";
import { AppInfoUserName } from "./Apps-styles";
import { Spacer } from "../../common/Spacer";
import { Box, Rating } from '@mui/material';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { getFee } from '../../background';
import { MyContext, getBaseApiReact } from '../../App';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { StarFilledIcon } from '../../assets/Icons/StarFilled';
import { StarEmptyIcon } from '../../assets/Icons/StarEmpty';
import { AppInfoUserName } from './Apps-styles';
import { Spacer } from '../../common/Spacer';
export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
const [value, setValue] = useState(0);
const { show } = useContext(MyContext);
const [hasPublishedRating, setHasPublishedRating] = useState<null | boolean>(
@@ -33,14 +27,14 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
const url = `${getBaseApiReact()}/polls/${pollName}`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
const responseData = await response.json();
if (responseData?.message?.includes("POLL_NO_EXISTS")) {
if (responseData?.message?.includes('POLL_NO_EXISTS')) {
setHasPublishedRating(false);
} else if (responseData?.pollName) {
setPollInfo(responseData);
@@ -48,9 +42,9 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
const responseVotes = await fetch(urlVotes, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
@@ -59,15 +53,15 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
const voteCount = responseDataVotes.voteCounts;
// Include initial value vote in the calculation
const ratingVotes = voteCount.filter(
(vote) => !vote.optionName.startsWith("initialValue-")
(vote) => !vote.optionName.startsWith('initialValue-')
);
const initialValueVote = voteCount.find((vote) =>
vote.optionName.startsWith("initialValue-")
vote.optionName.startsWith('initialValue-')
);
if (initialValueVote) {
// Convert "initialValue-X" to just "X" and add it to the ratingVotes array
const initialRating = parseInt(
initialValueVote.optionName.split("-")[1],
initialValueVote.optionName.split('-')[1],
10
);
ratingVotes.push({
@@ -92,11 +86,12 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
setValue(averageRating);
}
} catch (error) {
if (error?.message?.includes("POLL_NO_EXISTS")) {
if (error?.message?.includes('POLL_NO_EXISTS')) {
setHasPublishedRating(false);
}
}
}, []);
useEffect(() => {
if (hasCalledRef.current) return;
if (!app) return;
@@ -105,45 +100,48 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
const rateFunc = async (event, chosenValue, currentValue) => {
try {
const newValue = chosenValue || currentValue
if (!myName) throw new Error("You need a name to rate.");
const newValue = chosenValue || currentValue;
if (!myName) throw new Error('You need a name to rate.');
if (!app?.name) return;
const fee = await getFee("CREATE_POLL");
const fee = await getFee('CREATE_POLL');
await show({
message: `Would you like to rate this app a rating of ${newValue}?. It will create a POLL tx.`,
publishFee: fee.fee + " QORT",
publishFee: fee.fee + ' QORT',
});
if (hasPublishedRating === false) {
const pollName = `app-library-${app.service}-rating-${app.name}`;
const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`];
await new Promise((res, rej) => {
window.sendMessage("createPoll", {
pollName: pollName,
pollDescription: `Rating for ${app.service} ${app.name}`,
pollOptions: pollOptions,
pollOwnerAddress: myName,
}, 60000)
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
setInfoSnack({
type: "success",
message:
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
});
setOpenSnack(true);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
window
.sendMessage(
'createPoll',
{
pollName: pollName,
pollDescription: `Rating for ${app.service} ${app.name}`,
pollOptions: pollOptions,
pollOwnerAddress: myName,
},
60000
)
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
setInfoSnack({
type: 'success',
message:
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.',
});
setOpenSnack(true);
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
});
} else {
const pollName = `app-library-${app.service}-rating-${app.name}`;
@@ -152,39 +150,41 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
(option) => +option.optionName === +newValue
);
if (isNaN(optionIndex) || optionIndex === -1)
throw new Error("Cannot find rating option");
throw new Error('Cannot find rating option');
await new Promise((res, rej) => {
window.sendMessage("voteOnPoll", {
pollName: pollName,
optionIndex,
}, 60000)
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
setInfoSnack({
type: "success",
message:
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
});
setOpenSnack(true);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
window
.sendMessage(
'voteOnPoll',
{
pollName: pollName,
optionIndex,
},
60000
)
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
res(response);
setInfoSnack({
type: 'success',
message:
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.',
});
setOpenSnack(true);
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
});
}
} catch (error) {
console.log('error', error)
console.log('error', error);
setInfoSnack({
type: "error",
message: error?.message || "Unable to rate",
type: 'error',
message: error?.message || 'Unable to rate',
});
setOpenSnack(true);
}
@@ -194,17 +194,17 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
<div>
<Box
sx={{
display: "flex",
alignItems: "center",
flexDirection: ratingCountPosition === "top" ? "column" : "row",
display: 'flex',
alignItems: 'center',
flexDirection: ratingCountPosition === 'top' ? 'column' : 'row',
}}
>
{ratingCountPosition === "top" && (
{ratingCountPosition === 'top' && (
<>
<AppInfoUserName>
{(votesInfo?.totalVotes ?? 0) +
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{" "}
{" RATINGS"}
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{' '}
{' RATINGS'}
</AppInfoUserName>
<Spacer height="6px" />
<AppInfoUserName>{value?.toFixed(1)}</AppInfoUserName>
@@ -214,17 +214,17 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
<Rating
value={value}
onChange={(event, rating)=> rateFunc(event, rating, value)}
onChange={(event, rating) => rateFunc(event, rating, value)}
precision={1}
size="small"
icon={<StarFilledIcon />}
emptyIcon={<StarEmptyIcon />}
sx={{
display: "flex",
gap: "2px",
display: 'flex',
gap: '2px',
}}
/>
{ratingCountPosition === "right" && (
{ratingCountPosition === 'right' && (
<AppInfoUserName>
{(votesInfo?.totalVotes ?? 0) +
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}

View File

@@ -1,201 +1,251 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from 'react';
import { Box } from '@mui/material';
import { getBaseApiReact } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { useFrame } from 'react-frame-component';
import { useQortalMessageListener } from './useQortalMessageListener';
import { useThemeContext } from '../Theme/ThemeContext';
import { Avatar, Box, } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useFrame } from "react-frame-component";
import { useQortalMessageListener } from "./useQortalMessageListener";
export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, iframeRef) => {
const { rootHeight } = useContext(MyContext);
// const iframeRef = useRef(null);
const { document, window: frameWindow } = useFrame();
const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service, skipAuth)
const [url, setUrl] = useState('')
useEffect(()=> {
if(app?.isPreview) return
if(isDevMode){
setUrl(app?.url)
return
}
let hasQueryParam = false
if(app?.path && app.path.includes('?')){
hasQueryParam = true
}
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? "&": "?" }theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`)
}, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview])
useEffect(()=> {
if(app?.isPreview && app?.url){
resetHistory()
setUrl(app.url)
}
}, [app?.url, app?.isPreview])
const defaultUrl = useMemo(()=> {
return url
}, [url, isDevMode])
const refreshAppFunc = (e) => {
const {tabId} = e.detail
if(tabId === app?.tabId){
if(isDevMode){
resetHistory()
if(!app?.isPreview || app?.isPrivate){
setUrl(app?.url + `?time=${Date.now()}`)
}
return
}
const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=dark&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`
setUrl(constructUrl)
}
};
useEffect(() => {
subscribeToEvent("refreshApp", refreshAppFunc);
return () => {
unsubscribeFromEvent("refreshApp", refreshAppFunc);
};
}, [app, path, isDevMode]);
const removeTrailingSlash = (str) => str.replace(/\/$/, '');
const copyLinkFunc = (e) => {
const {tabId} = e.detail
if(tabId === app?.tabId){
let link = 'qortal://' + app?.service + '/' + app?.name
if(path && path.startsWith('/')){
link = link + removeTrailingSlash(path)
}
if(path && !path.startsWith('/')){
link = link + '/' + removeTrailingSlash(path)
}
navigator.clipboard.writeText(link)
.then(() => {
console.log("Path copied to clipboard:", path);
})
.catch((error) => {
console.error("Failed to copy path:", error);
});
}
};
useEffect(() => {
subscribeToEvent("copyLink", copyLinkFunc);
return () => {
unsubscribeFromEvent("copyLink", copyLinkFunc);
};
}, [app, path]);
// Function to navigate back in iframe
const navigateBackInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
// Calculate the previous index and path
const previousPageIndex = history.currentIndex - 1;
const previousPath = history.customQDNHistoryPaths[previousPageIndex];
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
// Signal non-manual navigation
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },targetOrigin
);
// Update the current index locally
changeCurrentIndex(previousPageIndex);
// Create a navigation promise with a 200ms timeout
const navigationPromise = new Promise((resolve, reject) => {
function handleNavigationSuccess(event) {
if (event.data?.action === 'NAVIGATION_SUCCESS' && event.data.path === previousPath) {
frameWindow.removeEventListener('message', handleNavigationSuccess);
resolve();
}
}
frameWindow.addEventListener('message', handleNavigationSuccess);
// Timeout after 200ms if no response
setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess);
reject(new Error("Navigation timeout"));
}, 200);
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, targetOrigin
export const AppViewer = React.forwardRef(
({ app, hide, isDevMode, skipAuth }, iframeRef) => {
// const iframeRef = useRef(null);
const { window: frameWindow } = useFrame();
const { path, history, changeCurrentIndex, resetHistory } =
useQortalMessageListener(
frameWindow,
iframeRef,
app?.tabId,
isDevMode,
app?.name,
app?.service,
skipAuth
);
});
const [url, setUrl] = useState('');
const { themeMode } = useThemeContext();
// Execute navigation promise and handle timeout fallback
try {
await navigationPromise;
} catch (error) {
if(isDevMode){
setUrl(`${url}${previousPath != null ? previousPath : ''}?theme=dark&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
return
}
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
useEffect(() => {
if (app?.isPreview) return;
if (isDevMode) {
setUrl(app?.url);
return;
}
let hasQueryParam = false;
if (app?.path && app.path.includes('?')) {
hasQueryParam = true;
}
const navigateBackAppFunc = (e) => {
setUrl(
`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? '&' : '?'}theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}`
);
}, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]);
navigateBackInIframe()
};
useEffect(() => {
if (app?.isPreview && app?.url) {
resetHistory();
setUrl(app.url);
}
}, [app?.url, app?.isPreview]);
const defaultUrl = useMemo(() => {
return url;
}, [url, isDevMode]);
useEffect(() => {
if(!app?.tabId) return
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
return () => {
unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
const refreshAppFunc = (e) => {
const { tabId } = e.detail;
if (tabId === app?.tabId) {
if (isDevMode) {
resetHistory();
if (!app?.isPreview || app?.isPrivate) {
setUrl(app?.url + `?time=${Date.now()}`);
}
return;
}
const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`;
setUrl(constructUrl);
}
};
}, [app, history]);
useEffect(() => {
subscribeToEvent('refreshApp', refreshAppFunc);
// Function to navigate back in iframe
const navigateForwardInIframe = async () => {
return () => {
unsubscribeFromEvent('refreshApp', refreshAppFunc);
};
}, [app, path, isDevMode]);
if (iframeRef.current && iframeRef.current.contentWindow) {
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_FORWARD'},
useEffect(() => {
const iframe = iframeRef?.current;
if (!iframe) return;
try {
const targetOrigin = new URL(iframe.src).origin;
iframe.contentWindow?.postMessage(
{ action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' },
targetOrigin
);
} else {
console.log('Iframe not accessible or does not have a content window.');
);
} catch (err) {
console.error('Failed to send theme change to iframe:', err);
}
}, [themeMode]);
const removeTrailingSlash = (str) => str.replace(/\/$/, '');
const copyLinkFunc = (e) => {
const { tabId } = e.detail;
if (tabId === app?.tabId) {
let link = 'qortal://' + app?.service + '/' + app?.name;
if (path && path.startsWith('/')) {
link = link + removeTrailingSlash(path);
}
if (path && !path.startsWith('/')) {
link = link + '/' + removeTrailingSlash(path);
}
navigator.clipboard
.writeText(link)
.then(() => {
console.log('Path copied to clipboard:', path);
})
.catch((error) => {
console.error('Failed to copy path:', error);
});
}
};
useEffect(() => {
subscribeToEvent('copyLink', copyLinkFunc);
return () => {
unsubscribeFromEvent('copyLink', copyLinkFunc);
};
}, [app, path]);
// Function to navigate back in iframe
const navigateBackInIframe = async () => {
if (
iframeRef.current &&
iframeRef.current.contentWindow &&
history?.currentIndex > 0
) {
// Calculate the previous index and path
const previousPageIndex = history.currentIndex - 1;
const previousPath = history.customQDNHistoryPaths[previousPageIndex];
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
// Signal non-manual navigation
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },
targetOrigin
);
// Update the current index locally
changeCurrentIndex(previousPageIndex);
// Create a navigation promise with a 200ms timeout
const navigationPromise = new Promise((resolve, reject) => {
function handleNavigationSuccess(event) {
if (
event.data?.action === 'NAVIGATION_SUCCESS' &&
event.data.path === previousPath
) {
frameWindow.removeEventListener(
'message',
handleNavigationSuccess
);
resolve();
}
}
frameWindow.addEventListener('message', handleNavigationSuccess);
// Timeout after 200ms if no response
setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess);
reject(new Error('Navigation timeout'));
}, 200);
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{
action: 'NAVIGATE_TO_PATH',
path: previousPath,
requestedHandler: 'UI',
},
targetOrigin
);
});
// Execute navigation promise and handle timeout fallback
try {
await navigationPromise;
} catch (error) {
if (isDevMode) {
setUrl(
`${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false`
);
return;
}
setUrl(
`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`
);
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
const navigateBackAppFunc = (e) => {
navigateBackInIframe();
};
useEffect(() => {
if (!app?.tabId) return;
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
return () => {
unsubscribeFromEvent(
`navigateBackApp-${app?.tabId}`,
navigateBackAppFunc
);
};
}, [app, history]);
// Function to navigate back in iframe
const navigateForwardInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow) {
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_FORWARD' },
targetOrigin
);
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
<iframe
ref={iframeRef}
style={{
height: '100vh',
border: 'none',
width: '100%',
}}
id="browser-iframe"
src={defaultUrl}
sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
allow="fullscreen; clipboard-read; clipboard-write"
></iframe>
</Box>
);
}
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
}}>
<iframe ref={iframeRef} style={{
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
border: 'none',
width: '100%'
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
allow="fullscreen; clipboard-read; clipboard-write">
</iframe>
</Box>
);
});
);

View File

@@ -1,26 +1,23 @@
import React, { useContext, } from 'react';
import React, { useContext } from 'react';
import { AppViewer } from './AppViewer';
import Frame from 'react-frame-component';
import { MyContext, isMobile } from '../../App';
import { MyContext } from '../../App';
const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
const { rootHeight } = useContext(MyContext);
return (
<Frame
id={`browser-iframe-${app?.tabId}`}
head={
<>
<style>
{`
const AppViewerContainer = React.forwardRef(
({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
return (
<Frame
id={`browser-iframe-${app?.tabId}`}
head={
<>
<style>
{`
body {
margin: 0;
padding: 0;
}
* {
-ms-overflow-style: none; /* IE and Edge */
msOverflowStyle: 'none', /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
*::-webkit-scrollbar {
@@ -28,24 +25,31 @@ const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode,
}
.frame-content {
overflow: hidden;
height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`};
height: 100vh;
}
`}
</style>
</>
}
style={{
position: (!isSelected || hide) && 'fixed',
left: (!isSelected || hide) && '-200vw',
height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`,
border: 'none',
width: '100%',
overflow: 'hidden',
}}
>
<AppViewer skipAuth={skipAuth} app={app} ref={ref} hide={!isSelected || hide} isDevMode={isDevMode} />
</Frame>
);
});
</style>
</>
}
style={{
border: 'none',
height: customHeight || '100vh',
left: (!isSelected || hide) && '-200vw',
overflow: 'hidden',
position: (!isSelected || hide) && 'fixed',
width: '100%',
}}
>
<AppViewer
skipAuth={skipAuth}
app={app}
ref={ref}
hide={!isSelected || hide}
isDevMode={isDevMode}
/>
</Frame>
);
}
);
export default AppViewerContainer;

View File

@@ -1,315 +1,397 @@
import {
AppBar,
Button,
Toolbar,
Typography,
Box,
TextField,
InputLabel,
ButtonBase,
} from "@mui/material";
import { styled } from "@mui/system";
export const AppsParent = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "center",
overflow: 'auto',
// For WebKit-based browsers (Chrome, Safari, etc.)
"::-webkit-scrollbar": {
width: "0px", // Set the width to 0 to hide the scrollbar
height: "0px", // Set the height to 0 for horizontal scrollbar
},
// For Firefox
scrollbarWidth: "none", // Hides the scrollbar in Firefox
// Optional for better cross-browser consistency
"-ms-overflow-style": "none" // Hides scrollbar in IE and Edge
}));
export const AppsContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-evenly',
gap: '24px',
flexWrap: 'wrap',
alignItems: 'flex-start',
alignSelf: 'center'
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
}));
export const AppsWidthLimiter = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'flex-start',
}));
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#434343',
borderRadius: '8px',
padding: '0px 10px',
height: '36px'
}));
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-start',
alignItems: 'center',
gap: '10px',
flexGrow: 1,
flexShrink: 0
}));
export const AppsSearchRight = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-end',
alignItems: 'center',
flexShrink: 1
}));
export const AppCircleContainer = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: '5px',
alignItems: 'center',
width: '100%'
}));
export const Add = styled(Typography)(({ theme }) => ({
fontSize: '36px',
fontWeight: 500,
lineHeight: '43.57px',
textAlign: 'left'
}));
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
// whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
import { Typography, Box, ButtonBase } from '@mui/material';
import { styled } from '@mui/system';
export const AppsParent = styled(Box)(({ theme }) => ({
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
height: '100%',
overflow: 'auto',
width: '100%',
// For WebKit-based browsers (Chrome, Safari, etc.)
'::-webkit-scrollbar': {
width: '0px', // Set the width to 0 to hide the scrollbar
height: '0px', // Set the height to 0 for horizontal scrollbar
},
// For Firefox
scrollbarWidth: 'none', // Hides the scrollbar in Firefox
// Optional for better cross-browser consistency
msOverflowStyle: 'none', // Hides scrollbar in IE and Edge
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsContainer = styled(Box)(({ theme }) => ({
alignItems: 'flex-start',
alignSelf: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexWrap: 'wrap',
gap: '24px',
justifyContent: 'space-evenly',
width: '90%',
}));
export const AppsDesktopLibraryHeader = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
width: '100%',
}));
export const AppsDesktopLibraryBody = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
width: '100%',
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
width: '100%',
}));
export const AppsWidthLimiter = styled(Box)(({ theme }) => ({
alignItems: 'flex-start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
width: '90%',
}));
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
borderRadius: '8px',
color: theme.palette.text.primary,
display: 'flex',
height: '36px',
justifyContent: 'space-between',
padding: '0px 10px',
width: '90%',
}));
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
flexGrow: 1,
flexShrink: 0,
gap: '10px',
justifyContent: 'flex-start',
width: '90%',
}));
export const AppsSearchRight = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
flexShrink: 1,
justifyContent: 'flex-end',
width: '90%',
}));
export const AppCircleContainer = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
gap: '5px',
width: '100%',
}));
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: '-webkit-box',
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
overflow: 'hidden',
textOverflow: 'ellipsis',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: '2',
width: '120%',
'-webkit-line-clamp': '2',
'-webkit-box-orient': 'vertical',
'display': '-webkit-box',
}));
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
}));
export const AppCircle = styled(Box)(({ theme }) => ({
display: "flex",
width: "75px",
flexDirection: "column",
height: "75px",
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
backgroundColor: "var(--apps-circle)",
border: '1px solid #FFFFFF'
}));
}));
export const AppInfoSnippetContainer = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'space-between',
alignItems: 'center',
width: '100%'
}));
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
}));
export const AppInfoSnippetLeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-start',
alignItems: 'center',
gap: '12px'
}));
export const AppInfoSnippetRight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const AppCircle = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.surface,
borderColor:
theme.palette.mode === 'dark'
? 'rgb(209, 209, 209)'
: 'rgba(41, 41, 43, 1)',
borderRadius: '50%',
borderStyle: 'solid',
borderWidth: '1px',
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
height: '75px',
justifyContent: 'center',
width: '75px',
}));
export const AppDownloadButton = styled(ButtonBase)(({ theme }) => ({
backgroundColor: "#247C0E",
width: '101px',
height: '29px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px',
alignSelf: 'center'
}));
export const AppInfoSnippetContainer = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}));
export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
}));
export const AppInfoSnippetLeft = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
gap: '12px',
justifyContent: 'flex-start',
}));
export const AppPublishTagsContainer = styled(Box)(({theme})=> ({
gap: '10px',
flexWrap: 'wrap',
justifyContent: 'flex-start',
width: '100%',
display: 'flex'
}))
export const AppInfoSnippetRight = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'flex-end',
}));
export const AppDownloadButton = styled(ButtonBase)(({ theme }) => ({
alignItems: 'center',
alignSelf: 'center',
backgroundColor: theme.palette.background.default,
borderRadius: '25px',
color: theme.palette.text.primary,
display: 'flex',
height: '29px',
justifyContent: 'center',
width: '101px',
}));
export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
justifyContent: 'center',
alignItems: 'flex-start',
}));
export const AppDownloadButtonText = styled(Typography)({
fontSize: '14px',
fontWeight: 500,
lineHeight: 1.2,
});
export const AppInfoAppName = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
textAlign: 'start'
}));
export const AppInfoUserName = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.2,
color: '#8D8F93',
textAlign: 'start'
}));
export const AppPublishTagsContainer = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
justifyContent: 'flex-start',
width: '100%',
}));
export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({
alignItems: 'flex-start',
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
color: theme.palette.text.primary,
}));
export const AppsNavBarParent = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
height: '60px',
backgroundColor: '#1F2023',
padding: '0px 10px',
position: "fixed",
bottom: 0,
zIndex: 1,
}));
export const AppInfoAppName = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
textAlign: 'start',
}));
export const AppsNavBarLeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-start',
alignItems: 'center',
flexGrow: 1
}));
export const AppsNavBarRight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const AppInfoUserName = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.2,
textAlign: 'start',
}));
export const TabParent = styled(Box)(({ theme }) => ({
height: '36px',
width: '36px',
backgroundColor: '#434343',
position: 'relative',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}));
export const AppsNavBarParent = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
bottom: 0,
color: theme.palette.text.primary,
display: 'flex',
height: '60px',
justifyContent: 'space-between',
padding: '0px 10px',
position: 'fixed',
width: '100%',
zIndex: 1,
}));
export const PublishQAppCTAParent = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
backgroundColor: '#181C23'
}));
export const AppsNavBarLeft = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-start',
}));
export const PublishQAppCTALeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-start',
alignItems: 'center',
}));
export const PublishQAppCTARight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const AppsNavBarRight = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'flex-end',
}));
export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({
width: '101px',
height: '29px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px',
border: '1px solid #FFFFFF'
}));
export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'center',
alignItems: 'center',
width: '60px',
height: '60px',
backgroundColor: '#4BBCFE'
}));
export const PublishQAppInfo = styled(Typography)(({ theme }) => ({
fontSize: '10px',
fontWeight: 400,
lineHeight: 1.2,
fontStyle: 'italic'
}));
export const TabParent = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
borderRadius: '50%',
color: theme.palette.text.primary,
display: 'flex',
height: '36px',
justifyContent: 'center',
position: 'relative',
width: '36px',
}));
export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
width: '101px',
height: '30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '5px',
backgroundColor: '#0091E1',
color: 'white',
fontWeight: 600,
fontSize: '10px'
}));
export const PublishQAppCTAParent = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}));
export const PublishQAppCTALeft = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'flex-start',
}));
export const AppsCategoryInfo = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: 'center',
width: '100%',
}));
export const PublishQAppCTARight = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
justifyContent: 'flex-end',
}));
export const AppsCategoryInfoSub = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: 'column',
}));
export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 700,
lineHeight: 1.2,
color: '#8D8F93',
}));
export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
color: '#8D8F93',
}));
export const AppsInfoDescription = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 300,
lineHeight: 1.2,
width: '90%',
textAlign: 'start'
}));
export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.text.primary,
borderRadius: '25px',
borderStyle: 'solid',
borderWidth: '1px',
color: theme.palette.text.primary,
display: 'flex',
height: '29px',
justifyContent: 'center',
width: '101px',
}));
export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
display: 'flex',
height: '80px',
justifyContent: 'center',
width: '60px',
}));
export const PublishQAppInfo = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontSize: '16px',
fontStyle: 'italic',
fontWeight: 400,
lineHeight: 1.2,
}));
export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
borderRadius: '5px',
color: theme.palette.text.primary,
display: 'flex',
fontSize: '16px',
fontWeight: 600,
height: '40px',
justifyContent: 'center',
width: '120px',
'&:hover': {
backgroundColor: 'action.hover', // background on hover
},
}));
export const AppsCategoryInfo = styled(Box)(({ theme }) => ({
alignItems: 'center',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
width: '100%',
}));
export const AppsCategoryInfoSub = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',
}));
export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontSize: '12px',
fontWeight: 700,
lineHeight: 1.2,
}));
export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));
export const AppsInfoDescription = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 300,
lineHeight: 1.2,
width: '90%',
textAlign: 'start',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
}));

View File

@@ -1,326 +0,0 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { AppsHome } from "./AppsHome";
import { Spacer } from "../../common/Spacer";
import { getBaseApiReact } from "../../App";
import { AppInfo } from "./AppInfo";
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from "../../utils/events";
import { AppsParent } from "./Apps-styles";
import AppViewerContainer from "./AppViewerContainer";
import ShortUniqueId from "short-unique-id";
import { AppPublish } from "./AppPublish";
import { AppsCategory } from "./AppsCategory";
import { AppsLibrary } from "./AppsLibrary";
const uid = new ShortUniqueId({ length: 8 });
export const Apps = ({ mode, setMode, show , myName}) => {
const [availableQapps, setAvailableQapps] = useState([]);
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null)
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([])
const iframeRefs = useRef({});
const myApp = useMemo(()=> {
return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
}, [myName, availableQapps])
const myWebsite = useMemo(()=> {
return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
}, [myName, availableQapps])
useEffect(() => {
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: tabs,
selectedTab: selectedTab,
isNewTabWindow: isNewTabWindow,
},
});
}, 100);
}, [show, tabs, selectedTab, isNewTabWindow]);
const getCategories = React.useCallback(async () => {
try {
const url = `${getBaseApiReact()}/arbitrary/categories`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response?.ok) return;
const responseData = await response.json();
setCategories(responseData);
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
const getQapps = React.useCallback(async () => {
try {
let apps = [];
let websites = [];
// dispatch(setIsLoadingGlobal(true))
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response?.ok) return;
const responseData = await response.json();
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const responseWebsites = await fetch(urlWebsites, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!responseWebsites?.ok) return;
const responseDataWebsites = await responseWebsites.json();
apps = responseData;
websites = responseDataWebsites;
const combine = [...apps, ...websites];
setAvailableQapps(combine);
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
useEffect(() => {
getQapps();
getCategories()
}, [getQapps, getCategories]);
const selectedAppInfoFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data);
setMode("appInfo");
};
useEffect(() => {
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
return () => {
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
};
}, []);
const selectedAppInfoCategoryFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data);
setMode("appInfo-from-category");
};
useEffect(() => {
subscribeToEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
return () => {
unsubscribeFromEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
};
}, []);
const selectedCategoryFunc = (e) => {
const data = e.detail?.data;
setSelectedCategory(data);
setMode("category");
};
useEffect(() => {
subscribeToEvent("selectedCategory", selectedCategoryFunc);
return () => {
unsubscribeFromEvent("selectedCategory", selectedCategoryFunc);
};
}, []);
const navigateBackFunc = (e) => {
if (['category', 'appInfo-from-category', 'appInfo', 'library', 'publish'].includes(mode)) {
// Handle the various modes as needed
if (mode === 'category') {
setMode('library');
setSelectedCategory(null);
} else if (mode === 'appInfo-from-category') {
setMode('category');
} else if (mode === 'appInfo') {
setMode('library');
} else if (mode === 'library') {
if (isNewTabWindow) {
setMode('viewer');
} else {
setMode('home');
}
} else if (mode === 'publish') {
setMode('library');
}
} else if(selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
}
};
useEffect(() => {
subscribeToEvent("navigateBack", navigateBackFunc);
return () => {
unsubscribeFromEvent("navigateBack", navigateBackFunc);
};
}, [mode, selectedTab]);
const addTabFunc = (e) => {
const data = e.detail?.data;
const newTab = {
...data,
tabId: uid.rnd(),
};
setTabs((prev) => [...prev, newTab]);
setSelectedTab(newTab);
setMode("viewer");
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("addTab", addTabFunc);
return () => {
unsubscribeFromEvent("addTab", addTabFunc);
};
}, [tabs]);
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
setSelectedTab(data);
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: tabs,
selectedTab: data,
isNewTabWindow: isNewTabWindow,
},
});
}, 100);
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
return () => {
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
};
}, [tabs, isNewTabWindow]);
const removeTabFunc = (e) => {
const data = e.detail?.data;
const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId);
if (copyTabs?.length === 0) {
setMode("home");
} else {
setSelectedTab(copyTabs[0]);
}
setTabs(copyTabs);
setSelectedTab(copyTabs[0]);
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: copyTabs,
selectedTab: copyTabs[0],
},
});
}, 400);
};
useEffect(() => {
subscribeToEvent("removeTab", removeTabFunc);
return () => {
unsubscribeFromEvent("removeTab", removeTabFunc);
};
}, [tabs]);
const setNewTabWindowFunc = (e) => {
setIsNewTabWindow(true);
setSelectedTab(null)
};
useEffect(() => {
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
return () => {
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
};
}, [tabs]);
return (
<AppsParent
sx={{
display: !show && "none",
}}
>
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
{mode === "home" && (
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
)}
<AppsLibrary
isShow={mode === "library" && !selectedTab}
availableQapps={availableQapps}
setMode={setMode}
myName={myName}
hasPublishApp={!!(myApp || myWebsite)}
categories={categories}
/>
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
{tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef();
}
return (
<AppViewerContainer
key={tab?.tabId}
hide={isNewTabWindow}
isSelected={tab?.tabId === selectedTab?.tabId}
app={tab}
ref={iframeRefs.current[tab.tabId]}
/>
);
})}
{isNewTabWindow && mode === "viewer" && (
<>
<Spacer height="30px" />
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
</>
)}
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
</AppsParent>
);
};

View File

@@ -1,193 +0,0 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
AppsWidthLimiter,
PublishQAppCTAButton,
PublishQAppCTALeft,
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
import qappDots from "../../assets/svgs/qappDots.svg";
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
const officialAppList = [
"q-tube",
"q-blog",
"q-share",
"q-support",
"q-mail",
"q-fund",
"q-shop",
"q-trade",
"q-support",
"q-manager",
"q-wallets",
"q-search",
"q-nodecontrol"
];
const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
export const AppsCategory = ({ availableQapps, myName, category, isShow }) => {
const [searchValue, setSearchValue] = useState("");
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
const categoryList = useMemo(() => {
return availableQapps.filter(
(app) =>
app?.metadata?.category === category?.id
);
}, [availableQapps, category]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
// Debounce logic
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 350);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
};
}, [searchValue]); // Runs effect when searchValue changes
// Example: Perform search or other actions based on debouncedValue
const searchedList = useMemo(() => {
if (!debouncedValue) return categoryList
return categoryList.filter((app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
);
}, [debouncedValue, categoryList]);
const rowRenderer = (index) => {
let app = searchedList[index];
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} isFromCategory={true} />;
};
return (
<AppsLibraryContainer sx={{
display: !isShow && 'none'
}}>
<AppsWidthLimiter>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "center",
}}
>
<AppsSearchContainer>
<AppsSearchLeft>
<img src={IconSearch} />
<InputBase
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
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>
</Box>
</AppsWidthLimiter>
<Spacer height="25px" />
<AppsWidthLimiter>
<AppLibrarySubTitle>{`Category: ${category?.name}`}</AppLibrarySubTitle>
<Spacer height="25px" />
</AppsWidthLimiter>
<AppsWidthLimiter>
<StyledVirtuosoContainer sx={{
height: rootHeight
}}>
<Virtuoso
ref={virtuosoRef}
data={searchedList}
itemContent={rowRenderer}
atBottomThreshold={50}
followOutput="smooth"
components={{
Scroller: ScrollerStyled // Use the styled scroller component
}}
/>
</StyledVirtuosoContainer>
</AppsWidthLimiter>
</AppsLibraryContainer>
);
};

View File

@@ -1,90 +1,52 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsDesktopLibraryBody,
AppsDesktopLibraryHeader,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
AppsWidthLimiter,
PublishQAppCTAButton,
PublishQAppCTALeft,
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
import qappDots from "../../assets/svgs/qappDots.svg";
} from './Apps-styles';
import { ButtonBase, InputBase, styled, useTheme } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import IconClearInput from '../../assets/svgs/ClearInput.svg';
import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from 'react-virtuoso';
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
import { AppsDesktopLibraryBody, AppsDesktopLibraryHeader } from "./AppsDesktop-styles";
const officialAppList = [
"q-tube",
"q-blog",
"q-share",
"q-support",
"q-mail",
"q-fund",
"q-shop",
"q-trade",
"q-support",
"q-manager",
"q-wallets",
"q-search",
"q-nodecontrol"
];
const ScrollerStyled = styled("div")({
const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
msOverflowStyle: 'none',
});
const StyledVirtuosoContainer = styled("div")({
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
msOverflowStyle: 'none',
});
export const AppsCategoryDesktop = ({
@@ -93,29 +55,28 @@ export const AppsCategoryDesktop = ({
category,
isShow,
}) => {
const [searchValue, setSearchValue] = useState("");
const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
const theme = useTheme();
const categoryList = useMemo(() => {
if(category?.id === 'all') return availableQapps
if (category?.id === 'all') return availableQapps;
return availableQapps.filter(
(app) => app?.metadata?.category === category?.id
);
}, [availableQapps, category]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 350);
setTimeout(() => {
virtuosoRef.current.scrollToIndex({
index: 0
});
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({ index: 0 });
}
}, 500);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
@@ -127,8 +88,13 @@ export const AppsCategoryDesktop = ({
const searchedList = useMemo(() => {
if (!debouncedValue) return categoryList;
return categoryList.filter((app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
return categoryList.filter(
(app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) ||
(app?.metadata?.title &&
app?.metadata?.title
?.toLowerCase()
.includes(debouncedValue.toLowerCase()))
);
}, [debouncedValue, categoryList]);
@@ -141,7 +107,7 @@ export const AppsCategoryDesktop = ({
myName={myName}
isFromCategory={true}
parentStyles={{
padding: '0px 10px'
padding: '0px 10px',
}}
/>
);
@@ -150,46 +116,56 @@ export const AppsCategoryDesktop = ({
return (
<AppsLibraryContainer
sx={{
display: !isShow && "none",
padding: "0px",
height: "100vh",
overflow: "hidden",
paddingTop: "30px",
display: !isShow && 'none',
height: '100vh',
overflow: 'hidden',
padding: '0px',
paddingTop: '30px',
}}
>
<AppsDesktopLibraryHeader
sx={{
maxWidth: "1500px",
width: "90%",
maxWidth: '1200px',
width: '90%',
}}
>
<AppsWidthLimiter
sx={{
alignItems: "flex-end",
alignItems: 'flex-end',
}}
>
<AppsSearchContainer sx={{
width: "412px",
}}>
<AppsSearchContainer
sx={{
width: '412px',
}}
>
<AppsSearchLeft>
<img src={IconSearch} />
<SearchIcon />
<InputBase
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
sx={{ ml: 1, flex: 1 }}
sx={{
background: theme.palette.background.paper,
borderRadius: '6px',
flex: 1,
ml: 1,
paddingLeft: '12px',
}}
placeholder="Search for apps"
inputProps={{
"aria-label": "Search for apps",
fontSize: "16px",
'aria-label': 'Search for apps',
fontSize: '16px',
fontWeight: 400,
}}
/>
</AppsSearchLeft>
<AppsSearchRight>
{searchValue && (
<ButtonBase
onClick={() => {
setSearchValue("");
setSearchValue('');
}}
>
<img src={IconClearInput} />
@@ -199,23 +175,27 @@ export const AppsCategoryDesktop = ({
</AppsSearchContainer>
</AppsWidthLimiter>
</AppsDesktopLibraryHeader>
<AppsDesktopLibraryBody
sx={{
alignItems: 'center',
height: `calc(100vh - 36px)`,
overflow: "auto",
padding: "0px",
alignItems: "center",
overflow: 'auto',
padding: '0px',
width: '70%',
}}
>
<Spacer height="25px" />
<AppsWidthLimiter>
<AppLibrarySubTitle>{`Category: ${category?.name}`}</AppLibrarySubTitle>
<Spacer height="25px" />
</AppsWidthLimiter>
<AppsWidthLimiter>
<StyledVirtuosoContainer
sx={{
sx={{
height: `calc(100vh - 36px - 90px - 25px)`,
}}
>

View File

@@ -1,24 +0,0 @@
import {
AppBar,
Button,
Toolbar,
Typography,
Box,
TextField,
InputLabel,
ButtonBase,
} from "@mui/material";
import { styled } from "@mui/system";
export const AppsDesktopLibraryHeader = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: 'column',
flexShrink: 0,
width: '100%'
}));
export const AppsDesktopLibraryBody = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: 'column',
flexGrow: 1,
width: '100%'
}));

View File

@@ -1,65 +1,78 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { AppsHomeDesktop } from "./AppsHomeDesktop";
import { Spacer } from "../../common/Spacer";
import { GlobalContext, MyContext, getBaseApiReact } from "../../App";
import { AppInfo } from "./AppInfo";
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { AppsHomeDesktop } from './AppsHomeDesktop';
import { Spacer } from '../../common/Spacer';
import { MyContext, getBaseApiReact } from '../../App';
import { AppInfo } from './AppInfo';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from "../../utils/events";
import { AppsParent } from "./Apps-styles";
import AppViewerContainer from "./AppViewerContainer";
import ShortUniqueId from "short-unique-id";
import { AppPublish } from "./AppPublish";
import { AppsLibraryDesktop } from "./AppsLibraryDesktop";
import { AppsCategoryDesktop } from "./AppsCategoryDesktop";
import { AppsNavBarDesktop } from "./AppsNavBarDesktop";
import { Box, ButtonBase } from "@mui/material";
import { HomeIcon } from "../../assets/Icons/HomeIcon";
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
import { Save } from "../Save/Save";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
import { CoreSyncStatus } from "../CoreSyncStatus";
import { IconWrapper } from "../Desktop/DesktopFooter";
import AppIcon from "../../assets/svgs/AppIcon.svg";
import { useRecoilState } from "recoil";
import { enabledDevModeAtom } from "../../atoms/global";
import { AppsIcon } from "../../assets/Icons/AppsIcon";
} from '../../utils/events';
import { AppsParent } from './Apps-styles';
import AppViewerContainer from './AppViewerContainer';
import ShortUniqueId from 'short-unique-id';
import { AppPublish } from './AppPublish';
import { AppsLibraryDesktop } from './AppsLibraryDesktop';
import { AppsCategoryDesktop } from './AppsCategoryDesktop';
import { AppsNavBarDesktop } from './AppsNavBarDesktop';
import { Box, ButtonBase, useTheme } from '@mui/material';
import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { Save } from '../Save/Save';
import { IconWrapper } from '../Desktop/DesktopFooter';
import { enabledDevModeAtom } from '../../atoms/global';
import { AppsIcon } from '../../assets/Icons/AppsIcon';
import { CoreSyncStatus } from '../CoreSyncStatus';
import { MessagingIconFilled } from '../../assets/Icons/MessagingIconFilled';
import { useAtom } from 'jotai';
const uid = new ShortUniqueId({ length: 8 });
export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktopSideView, hasUnreadDirects, isDirects, isGroups, hasUnreadGroups, toggleSideViewGroups, toggleSideViewDirects, setDesktopViewMode, isApps, desktopViewMode}) => {
export const AppsDesktop = ({
mode,
setMode,
show,
myName,
goToHome,
hasUnreadDirects,
hasUnreadGroups,
setDesktopViewMode,
desktopViewMode,
}) => {
const [availableQapps, setAvailableQapps] = useState([]);
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null)
const [selectedCategory, setSelectedCategory] = useState(null);
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([])
const [categories, setCategories] = useState([]);
const iframeRefs = useRef({});
const [isEnabledDevMode, setIsEnabledDevMode] = useRecoilState(enabledDevModeAtom)
const { showTutorial } = useContext(GlobalContext);
const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
const myApp = useMemo(()=> {
return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
}, [myName, availableQapps])
const myWebsite = useMemo(()=> {
return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
}, [myName, availableQapps])
const { showTutorial } = useContext(MyContext);
const theme = useTheme();
const myApp = useMemo(() => {
return availableQapps.find(
(app) => app.name === myName && app.service === 'APP'
);
}, [myName, availableQapps]);
useEffect(()=> {
if(show){
showTutorial('qapps')
const myWebsite = useMemo(() => {
return availableQapps.find(
(app) => app.name === myName && app.service === 'WEBSITE'
);
}, [myName, availableQapps]);
useEffect(() => {
if (show) {
showTutorial('qapps');
}
}, [show])
}, [show]);
useEffect(() => {
setTimeout(() => {
executeEvent("setTabsToNav", {
executeEvent('setTabsToNav', {
data: {
tabs: tabs,
selectedTab: selectedTab,
@@ -74,17 +87,17 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const url = `${getBaseApiReact()}/arbitrary/categories`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if (!response?.ok) return;
const responseData = await response.json();
setCategories(responseData);
} catch (error) {
console.log(error);
} finally {
// dispatch(setIsLoadingGlobal(false))
}
@@ -98,9 +111,9 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if (!response?.ok) return;
@@ -108,33 +121,37 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const responseWebsites = await fetch(urlWebsites, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if (!responseWebsites?.ok) return;
const responseDataWebsites = await responseWebsites.json();
apps = responseData;
websites = responseDataWebsites;
const combine = [...apps, ...websites];
setAvailableQapps(combine);
} catch (error) {
console.log(error);
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
useEffect(() => {
getCategories()
getCategories();
}, [getCategories]);
useEffect(() => {
getQapps();
const interval = setInterval(() => {
getQapps();
}, 20 * 60 * 1000); // 20 minutes in milliseconds
const interval = setInterval(
() => {
getQapps();
},
20 * 60 * 1000
); // 20 minutes in milliseconds
return () => clearInterval(interval);
}, [getQapps]);
@@ -142,54 +159,58 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const selectedAppInfoFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data);
setMode("appInfo");
setMode('appInfo');
};
useEffect(() => {
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
subscribeToEvent('selectedAppInfo', selectedAppInfoFunc);
return () => {
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
unsubscribeFromEvent('selectedAppInfo', selectedAppInfoFunc);
};
}, []);
const selectedAppInfoCategoryFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data);
setMode("appInfo-from-category");
setMode('appInfo-from-category');
};
useEffect(() => {
subscribeToEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
subscribeToEvent('selectedAppInfoCategory', selectedAppInfoCategoryFunc);
return () => {
unsubscribeFromEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
unsubscribeFromEvent(
'selectedAppInfoCategory',
selectedAppInfoCategoryFunc
);
};
}, []);
const selectedCategoryFunc = (e) => {
const data = e.detail?.data;
setSelectedCategory(data);
setMode("category");
setMode('category');
};
useEffect(() => {
subscribeToEvent("selectedCategory", selectedCategoryFunc);
subscribeToEvent('selectedCategory', selectedCategoryFunc);
return () => {
unsubscribeFromEvent("selectedCategory", selectedCategoryFunc);
unsubscribeFromEvent('selectedCategory', selectedCategoryFunc);
};
}, []);
const navigateBackFunc = (e) => {
if (['category', 'appInfo-from-category', 'appInfo', 'library', 'publish'].includes(mode)) {
if (
[
'category',
'appInfo-from-category',
'appInfo',
'library',
'publish',
].includes(mode)
) {
// Handle the various modes as needed
if (mode === 'category') {
setMode('library');
@@ -207,17 +228,16 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
} else if (mode === 'publish') {
setMode('library');
}
} else if(selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
} else if (selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {});
}
};
useEffect(() => {
subscribeToEvent("navigateBack", navigateBackFunc);
subscribeToEvent('navigateBack', navigateBackFunc);
return () => {
unsubscribeFromEvent("navigateBack", navigateBackFunc);
unsubscribeFromEvent('navigateBack', navigateBackFunc);
};
}, [mode, selectedTab]);
@@ -229,27 +249,25 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
};
setTabs((prev) => [...prev, newTab]);
setSelectedTab(newTab);
setMode("viewer");
setMode('viewer');
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("addTab", addTabFunc);
subscribeToEvent('addTab', addTabFunc);
return () => {
unsubscribeFromEvent("addTab", addTabFunc);
unsubscribeFromEvent('addTab', addTabFunc);
};
}, [tabs]);
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
if(e.detail?.isDevMode) return
if (e.detail?.isDevMode) return;
setSelectedTab(data);
setTimeout(() => {
executeEvent("setTabsToNav", {
executeEvent('setTabsToNav', {
data: {
tabs: tabs,
selectedTab: data,
@@ -259,13 +277,12 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
}, 100);
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
subscribeToEvent('setSelectedTab', setSelectedTabFunc);
return () => {
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
unsubscribeFromEvent('setSelectedTab', setSelectedTabFunc);
};
}, [tabs, isNewTabWindow]);
@@ -273,14 +290,14 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const data = e.detail?.data;
const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId);
if (copyTabs?.length === 0) {
setMode("home");
setMode('home');
} else {
setSelectedTab(copyTabs[0]);
}
setTabs(copyTabs);
setSelectedTab(copyTabs[0]);
setTimeout(() => {
executeEvent("setTabsToNav", {
executeEvent('setTabsToNav', {
data: {
tabs: copyTabs,
selectedTab: copyTabs[0],
@@ -290,23 +307,23 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
};
useEffect(() => {
subscribeToEvent("removeTab", removeTabFunc);
subscribeToEvent('removeTab', removeTabFunc);
return () => {
unsubscribeFromEvent("removeTab", removeTabFunc);
unsubscribeFromEvent('removeTab', removeTabFunc);
};
}, [tabs]);
const setNewTabWindowFunc = (e) => {
setIsNewTabWindow(true);
setSelectedTab(null)
setSelectedTab(null);
};
useEffect(() => {
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
subscribeToEvent('newTabWindow', setNewTabWindowFunc);
return () => {
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
unsubscribeFromEvent('newTabWindow', setNewTabWindowFunc);
};
}, [tabs]);
@@ -315,167 +332,153 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
sx={{
position: !show && 'fixed',
left: !show && '-200vw',
flexDirection: 'row'
flexDirection: 'row',
}}
>
<Box sx={{
width: '60px',
flexDirection: 'column',
height: '100vh',
alignItems: 'center',
display: 'flex',
gap: '25px'
}}>
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
gap: '25px',
height: '100vh',
width: '60px',
backgroundColor: theme.palette.background.surface,
borderRight: `1px solid ${theme.palette.border.subtle}`,
}}
>
<ButtonBase
sx={{
width: '70px',
height: '70px',
paddingTop: '23px',
}}
>
<CoreSyncStatus />
</ButtonBase>
<ButtonBase
sx={{
width: '60px',
height: '60px',
paddingTop: '23px'
}}
onClick={() => {
goToHome();
}}
>
<HomeIcon
height={34}
color={desktopViewMode === 'home' ? 'white': "rgba(250, 250, 250, 0.5)"}
/>
<HomeIcon height={34} color={theme.palette.text.secondary} />
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('apps')
setDesktopViewMode('apps');
}}
>
<IconWrapper label="Apps" disableWidth>
<AppsIcon height={30} color={theme.palette.text.primary} />
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('chat');
}}
>
<IconWrapper
color={isApps ? 'white' :"rgba(250, 250, 250, 0.5)"}
label="Apps"
disableWidth
>
<AppsIcon height={30} color={isApps ? 'white' :"rgba(250, 250, 250, 0.5)"} />
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('chat')
}}
>
<IconWrapper
color={(hasUnreadDirects || hasUnreadGroups) ? "var(--unread)" : desktopViewMode === 'chat' ? 'white' :"rgba(250, 250, 250, 0.5)"}
color={
hasUnreadDirects || hasUnreadGroups
? theme.palette.other.unread
: desktopViewMode === 'chat'
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Chat"
disableWidth
>
<MessagingIcon
<MessagingIconFilled
height={30}
color={
(hasUnreadDirects || hasUnreadGroups)
? "var(--unread)"
hasUnreadDirects || hasUnreadGroups
? theme.palette.other.unread
: desktopViewMode === 'chat'
? "white"
: "rgba(250, 250, 250, 0.5)"
? theme.palette.text.primary
: theme.palette.text.secondary
}
/>
</IconWrapper>
</IconWrapper>
</ButtonBase>
{/* <ButtonBase
onClick={() => {
setDesktopSideView("directs");
toggleSideViewDirects()
}}
>
<MessagingIcon
height={30}
color={
hasUnreadDirects
? "var(--danger)"
: isDirects
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView("groups");
toggleSideViewGroups()
}}
>
<HubsIcon
height={30}
color={
hasUnreadGroups
? "var(--danger)"
: isGroups
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</ButtonBase> */}
<Save isDesktop disableWidth myName={myName}/>
<Save isDesktop disableWidth myName={myName} />
{isEnabledDevMode && (
<ButtonBase
onClick={() => {
setDesktopViewMode('dev')
}}
>
<IconWrapper
color={desktopViewMode === 'dev' ? 'white' : "rgba(250, 250, 250, 0.5)"}
label="Dev"
disableWidth
>
<AppsIcon color={desktopViewMode === 'dev' ? 'white' : "rgba(250, 250, 250, 0.5)"} height={30} />
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopViewMode('dev');
}}
>
<IconWrapper label="Dev" disableWidth>
<AppsIcon height={30} />
</IconWrapper>
</ButtonBase>
)}
{mode !== 'home' && (
<AppsNavBarDesktop disableBack={isNewTabWindow && mode === 'viewer'} />
<AppsNavBarDesktop
disableBack={isNewTabWindow && mode === 'viewer'}
/>
)}
</Box>
</Box>
{mode === "home" && (
<Box sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto'
}}>
<Spacer height="30px" />
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
{mode === 'home' && (
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
}}
>
<Spacer height="30px" />
<AppsHomeDesktop
myName={myName}
availableQapps={availableQapps}
setMode={setMode}
myApp={myApp}
myWebsite={myWebsite}
/>
</Box>
)}
<AppsLibraryDesktop
isShow={mode === "library" && !selectedTab}
availableQapps={availableQapps}
setMode={setMode}
myName={myName}
hasPublishApp={!!(myApp || myWebsite)}
categories={categories}
getQapps={getQapps}
/>
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
<AppsCategoryDesktop availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
<AppsLibraryDesktop
availableQapps={availableQapps}
categories={categories}
getQapps={getQapps}
hasPublishApp={!!(myApp || myWebsite)}
isShow={mode === 'library' && !selectedTab}
myName={myName}
setMode={setMode}
/>
{mode === 'appInfo' && !selectedTab && (
<AppInfo app={selectedAppInfo} myName={myName} />
)}
{mode === 'appInfo-from-category' && !selectedTab && (
<AppInfo app={selectedAppInfo} myName={myName} />
)}
<AppsCategoryDesktop
availableQapps={availableQapps}
isShow={mode === 'category' && !selectedTab}
category={selectedCategory}
myName={myName}
/>
{mode === 'publish' && !selectedTab && (
<AppPublish names={myName ? [myName] : []} categories={categories} />
)}
{tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef();
}
return (
<AppViewerContainer
key={tab?.tabId}
key={tab?.tabId}
hide={isNewTabWindow}
isSelected={tab?.tabId === selectedTab?.tabId}
app={tab}
@@ -485,18 +488,25 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
);
})}
{isNewTabWindow && mode === "viewer" && (
{isNewTabWindow && mode === 'viewer' && (
<>
<Box sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto'
}}>
<Spacer height="30px" />
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
}}
>
<Spacer height="30px" />
<AppsHomeDesktop
myName={myName}
availableQapps={availableQapps}
setMode={setMode}
myApp={myApp}
myWebsite={myWebsite}
/>
</Box>
</>
)}

View File

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

View File

@@ -1,4 +1,4 @@
import React, { useContext, useMemo, useState } from "react";
import React, { useContext, useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@@ -6,8 +6,8 @@ import {
AppLibrarySubTitle,
AppsContainer,
AppsParent,
} from "./Apps-styles";
import { Buffer } from "buffer";
} from './Apps-styles';
import { Buffer } from 'buffer';
import {
Avatar,
@@ -20,17 +20,17 @@ import {
DialogContentText,
DialogTitle,
Input,
} from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
import { useModal } from "../../common/useModal";
import { createEndpoint, isUsingLocal } from "../../background";
import { Label } from "../Group/AddGroup";
import ShortUniqueId from "short-unique-id";
import swaggerSVG from '../../assets/svgs/swagger.svg'
} from '@mui/material';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import { useModal } from '../../common/useModal';
import { createEndpoint, isUsingLocal } from '../../background';
import { Label } from '../Group/AddGroup';
import ShortUniqueId from 'short-unique-id';
import swaggerSVG from '../../assets/svgs/swagger.svg';
const uid = new ShortUniqueId({ length: 8 });
export const AppsDevModeHome = ({
@@ -40,8 +40,8 @@ export const AppsDevModeHome = ({
availableQapps,
myName,
}) => {
const [domain, setDomain] = useState("127.0.0.1");
const [port, setPort] = useState("");
const [domain, setDomain] = useState('127.0.0.1');
const [port, setPort] = useState('');
const [selectedPreviewFile, setSelectedPreviewFile] = useState(null);
const { isShow, onCancel, onOk, show, message } = useModal();
@@ -58,7 +58,7 @@ export const AppsDevModeHome = ({
const content = await window.electron.readFile(filePath);
return { buffer: content, filePath };
} else {
console.log("No file selected.");
console.log('No file selected.');
}
};
const handleSelectDirectry = async (existingDirectoryPath) => {
@@ -67,7 +67,7 @@ export const AppsDevModeHome = ({
if (buffer) {
return { buffer, directoryPath };
} else {
console.log("No file selected.");
console.log('No file selected.');
}
};
@@ -78,34 +78,36 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
const { portVal, domainVal } = await show({
message: "",
publishFee: "",
message: '',
publishFee: '',
});
const framework = domainVal + ":" + portVal;
const framework = domainVal + ':' + portVal;
const response = await fetch(
`${getBaseApiReact()}/developer/proxy/start`,
{
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: framework,
}
);
const responseData = await response.text();
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:" + responseData,
url: 'http://127.0.0.1:' + responseData,
},
});
} catch (error) {}
} catch (error) {
console.log(error);
}
};
const addPreviewApp = async (isRefresh, existingFilePath, tabId) => {
@@ -115,9 +117,9 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
@@ -125,8 +127,8 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "You need a name to use preview",
type: 'error',
message: 'You need a name to use preview',
});
return;
}
@@ -137,29 +139,29 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "Please select a file",
type: 'error',
message: 'Please select a file',
});
return;
}
const postBody = Buffer.from(buffer).toString("base64");
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: postBody,
});
if (!response?.ok) throw new Error("Invalid zip");
if (!response?.ok) throw new Error('Invalid zip');
const previewPath = await response.text();
if (tabId) {
executeEvent("appsDevModeUpdateTab", {
executeEvent('appsDevModeUpdateTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
filePath,
refreshFunc: (tabId) => {
@@ -170,9 +172,9 @@ export const AppsDevModeHome = ({
});
return;
}
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
filePath,
refreshFunc: (tabId) => {
@@ -192,9 +194,9 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
type: 'error',
message:
"Please use your local node for dev mode! Logout and use Local node.",
'Please use your local node for dev mode! Logout and use Local node.',
});
return;
}
@@ -202,8 +204,8 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "You need a name to use preview",
type: 'error',
message: 'You need a name to use preview',
});
return;
}
@@ -214,29 +216,29 @@ export const AppsDevModeHome = ({
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: "Please select a file",
type: 'error',
message: 'Please select a file',
});
return;
}
const postBody = Buffer.from(buffer).toString("base64");
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: postBody,
});
if (!response?.ok) throw new Error("Invalid zip");
if (!response?.ok) throw new Error('Invalid zip');
const previewPath = await response.text();
if (tabId) {
executeEvent("appsDevModeUpdateTab", {
executeEvent('appsDevModeUpdateTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
directoryPath,
refreshFunc: (tabId) => {
@@ -247,9 +249,9 @@ export const AppsDevModeHome = ({
});
return;
}
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391" + previewPath,
url: 'http://127.0.0.1:12391' + previewPath,
isPreview: true,
directoryPath,
refreshFunc: (tabId) => {
@@ -266,22 +268,24 @@ export const AppsDevModeHome = ({
<>
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Dev Mode Apps
</AppLibrarySubTitle>
</AppsContainer>
<Spacer height="45px" />
<AppsContainer
sx={{
gap: "75px",
justifyContent: "flex-start",
gap: '75px',
justifyContent: 'flex-start',
}}
>
<ButtonBase
@@ -291,7 +295,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
@@ -300,6 +304,7 @@ export const AppsDevModeHome = ({
<AppCircleLabel>Server</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
addPreviewApp();
@@ -307,15 +312,17 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
<Add>+</Add>
</AppCircle>
<AppCircleLabel>Zip</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
addPreviewAppWithDirectory();
@@ -323,7 +330,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
@@ -332,12 +339,13 @@ export const AppsDevModeHome = ({
<AppCircleLabel>Directory</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
service: "APP",
name: "Q-Sandbox",
service: 'APP',
name: 'Q-Sandbox',
tabId: uid.rnd(),
},
});
@@ -345,16 +353,16 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
<Avatar
sx={{
height: "42px",
width: "42px",
"& img": {
objectFit: "fill",
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt="Q-Sandbox"
@@ -362,39 +370,41 @@ export const AppsDevModeHome = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>Q-Sandbox</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent("appsDevModeAddTab", {
executeEvent('appsDevModeAddTab', {
data: {
url: "http://127.0.0.1:12391",
url: 'http://127.0.0.1:12391',
isPreview: false,
customIcon: swaggerSVG
customIcon: swaggerSVG,
},
});
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
<Avatar
sx={{
height: "42px",
width: "42px",
"& img": {
objectFit: "fill",
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt="API"
@@ -402,37 +412,40 @@ export const AppsDevModeHome = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>API</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
</AppsContainer>
{isShow && (
<Dialog
open={isShow}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
onKeyDown={(e) => {
if (e.key === "Enter" && domain && port) {
if (e.key === 'Enter' && domain && port) {
onOk({ portVal: port, domainVal: domain });
}
}}
>
<DialogTitle id="alert-dialog-title">
{"Add custom framework"}
{'Add custom framework'}
</DialogTitle>
<DialogContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Domain</Label>
@@ -444,10 +457,10 @@ export const AppsDevModeHome = ({
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>Port</Label>
@@ -458,6 +471,7 @@ export const AppsDevModeHome = ({
/>
</Box>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onCancel}>
Close

View File

@@ -1,51 +1,35 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
AppsNavBarLeft,
AppsNavBarParent,
AppsNavBarRight,
} from "./Apps-styles";
import NavBack from "../../assets/svgs/NavBack.svg";
import NavAdd from "../../assets/svgs/NavAdd.svg";
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
import {
ButtonBase,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Tab,
Tabs,
} from "@mui/material";
} from './Apps-styles';
import { NavBack } from '../../assets/Icons/NavBack.tsx';
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
import { ButtonBase, Tab, Tabs, useTheme } from '@mui/material';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from "../../utils/events";
import TabComponent from "./TabComponent";
import PushPinIcon from "@mui/icons-material/PushPin";
import RefreshIcon from "@mui/icons-material/Refresh";
import { useRecoilState, useSetRecoilState } from "recoil";
import {
navigationControllerAtom,
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from "../../atoms/global";
import { AppsDevModeTabComponent } from "./AppsDevModeTabComponent";
} from '../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh';
import { navigationControllerAtom } from '../../atoms/global';
import { AppsDevModeTabComponent } from './AppsDevModeTabComponent';
import { useAtom } from 'jotai';
export const AppsDevModeNavBar = () => {
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom
);
const theme = useTheme();
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
@@ -57,27 +41,25 @@ export const AppsDevModeNavBar = () => {
useEffect(() => {
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
if (tabsRef.current) {
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
if (tabElements.length > 0) {
const lastTab = tabElements[tabElements.length - 1];
lastTab.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "end",
behavior: 'smooth',
block: 'nearest',
inline: 'end',
});
}
}
}, [tabs.length]); // Dependency on the number of tabs
const isDisableBackButton = useMemo(()=> {
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
return false
}, [navigationController, selectedTab])
const isDisableBackButton = useMemo(() => {
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
return false;
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
return true;
return false;
}, [navigationController, selectedTab]);
const setTabsToNav = (e) => {
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
@@ -88,45 +70,43 @@ export const AppsDevModeNavBar = () => {
};
useEffect(() => {
subscribeToEvent("appsDevModeSetTabsToNav", setTabsToNav);
subscribeToEvent('appsDevModeSetTabsToNav', setTabsToNav);
return () => {
unsubscribeFromEvent("appsDevModeSetTabsToNav", setTabsToNav);
unsubscribeFromEvent('appsDevModeSetTabsToNav', setTabsToNav);
};
}, []);
return (
<AppsNavBarParent
sx={{
position: "relative",
flexDirection: "column",
width: "60px",
height: "unset",
maxHeight: "70vh",
borderRadius: "0px 30px 30px 0px",
padding: "10px",
position: 'relative',
flexDirection: 'column',
width: '59px',
height: 'unset',
maxHeight: '70vh',
borderRadius: '0px 30px 30px 0px',
padding: '10px',
}}
>
<AppsNavBarLeft
sx={{
flexDirection: "column",
flexDirection: 'column',
}}
>
<ButtonBase
onClick={() => {
executeEvent("devModeNavigateBack", selectedTab?.tabId);
executeEvent('devModeNavigateBack', selectedTab?.tabId);
}}
disabled={isDisableBackButton}
sx={{
opacity: !isDisableBackButton ? 1 : 0.1,
cursor: !isDisableBackButton ? 'pointer': 'default'
cursor: !isDisableBackButton ? 'pointer' : 'default',
}}
>
<img src={NavBack} />
<NavBack />
</ButtonBase>
<Tabs
orientation="vertical"
ref={tabsRef}
@@ -134,11 +114,11 @@ export const AppsDevModeNavBar = () => {
variant="scrollable" // Make tabs scrollable
scrollButtons={true}
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.text.primary,
},
maxHeight: `320px`, // Ensure the tabs container fits within the available space
overflow: "hidden", // Prevents overflow on small screens
maxHeight: `275px`, // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
}}
>
{tabs?.map((tab) => (
@@ -153,65 +133,61 @@ export const AppsDevModeNavBar = () => {
/>
} // Pass custom component
sx={{
"&.Mui-selected": {
color: "white",
'&.Mui-selected': {
color: theme.palette.text.primary,
},
padding: "0px",
margin: "0px",
minWidth: "0px",
width: "50px",
padding: '0px',
margin: '0px',
minWidth: '0px',
width: '50px',
}}
/>
))}
</Tabs>
</AppsNavBarLeft>
{selectedTab && (
<AppsNavBarRight
sx={{
gap: "10px",
flexDirection: "column",
}}
>
<ButtonBase
onClick={() => {
setSelectedTab(null);
executeEvent("devModeNewTabWindow", {});
sx={{
gap: '10px',
flexDirection: 'column',
}}
>
<img
style={{
height: "40px",
width: "40px",
<ButtonBase
onClick={() => {
setSelectedTab(null);
executeEvent('devModeNewTabWindow', {});
}}
src={NavAdd}
/>
</ButtonBase>
<ButtonBase
onClick={(e) => {
if(selectedTab?.refreshFunc){
selectedTab.refreshFunc(selectedTab?.tabId)
} else {
executeEvent("refreshApp", {
tabId: selectedTab?.tabId,
});
}
}}
>
<RefreshIcon
sx={{
color: "rgba(250, 250, 250, 0.5)",
>
<NavAdd
style={{
height: '40px',
width: '40px',
height: 'auto'
}}
/>
</ButtonBase>
</AppsNavBarRight>
</ButtonBase>
<ButtonBase
onClick={(e) => {
if (selectedTab?.refreshFunc) {
selectedTab.refreshFunc(selectedTab?.tabId);
} else {
executeEvent('refreshApp', {
tabId: selectedTab?.tabId,
});
}
}}
>
<RefreshIcon
sx={{
color: 'rgba(250, 250, 250, 0.5)',
width: '40px',
height: 'auto',
}}
/>
</ButtonBase>
</AppsNavBarRight>
)}
</AppsNavBarParent>
);
};

View File

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

View File

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

View File

@@ -1,143 +1,154 @@
import React, { useMemo, useState } from "react";
import { useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsParent,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, Input } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
import { SortablePinnedApps } from "./SortablePinnedApps";
import { extractComponents } from "../Chat/MessageDisplay";
} from './Apps-styles';
import { Box, ButtonBase, Input, useTheme } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import { SortablePinnedApps } from './SortablePinnedApps';
import { extractComponents } from '../Chat/MessageDisplay';
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
import { AppsPrivate } from "./AppsPrivate";
import { AppsPrivate } from './AppsPrivate';
import ThemeSelector from '../Theme/ThemeSelector';
import LanguageSelector from '../Language/LanguageSelector';
export const AppsHomeDesktop = ({
setMode,
myApp,
myWebsite,
availableQapps,
myName
myName,
}) => {
const [qortalUrl, setQortalUrl] = useState('')
const [qortalUrl, setQortalUrl] = useState('');
const theme = useTheme();
const openQortalUrl = ()=> {
const openQortalUrl = () => {
try {
if(!qortalUrl) return
if (!qortalUrl) return;
const res = extractComponents(qortalUrl);
if (res) {
const { service, name, identifier, path } = res;
executeEvent("addTab", { data: { service, name, identifier, path } });
executeEvent("open-apps-mode", { });
setQortalUrl('qortal://')
executeEvent('addTab', { data: { service, name, identifier, path } });
executeEvent('open-apps-mode', {});
setQortalUrl('qortal://');
}
} catch (error) {
console.log(error);
}
}
};
return (
<>
<AppsContainer
sx={{
justifyContent: "flex-start",
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
}}
>
Apps Dashboard
</AppLibrarySubTitle>
</AppsContainer>
<Spacer height="20px" />
<AppsContainer
sx={{
justifyContent: "flex-start",
justifyContent: 'flex-start',
}}
>
<Box sx={{
display: 'flex',
gap: '20px',
alignItems: 'center',
backgroundColor: '#1f2023',
padding: '7px',
borderRadius: '20px',
width: '100%',
maxWidth: '500px'
}}>
<Input
id="standard-adornment-name"
value={qortalUrl}
onChange={(e) => {
setQortalUrl(e.target.value)
}}
disableUnderline
autoComplete='off'
autoCorrect='off'
placeholder="qortal://"
<AppLibrarySubTitle
sx={{
fontSize: '30px',
}}
>
Apps Dashboard
</AppLibrarySubTitle>
</AppsContainer>
<Spacer height="20px" />
<AppsContainer
sx={{
justifyContent: 'flex-start',
}}
>
<Box
sx={{
display: 'flex',
gap: '20px',
alignItems: 'center',
backgroundColor: theme.palette.background.paper,
padding: '7px',
borderRadius: '20px',
width: '100%',
maxWidth: '500px',
}}
>
<Input
id="standard-adornment-name"
value={qortalUrl}
onChange={(e) => {
setQortalUrl(e.target.value);
}}
disableUnderline
autoComplete="off"
autoCorrect="off"
placeholder="qortal://"
sx={{
width: '100%',
color: theme.palette.text.primary,
'& .MuiInput-input::placeholder': {
color: theme.palette.text.secondary,
fontSize: '20px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '120%', // 24px
letterSpacing: '0.15px',
opacity: 1,
},
'&:focus': {
outline: 'none',
},
// Add any additional styles for the input here
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && qortalUrl) {
openQortalUrl();
}
}}
/>
<ButtonBase onClick={() => openQortalUrl()}>
<ArrowOutwardIcon
sx={{
width: '100%',
color: 'white',
'& .MuiInput-input::placeholder': {
color: 'rgba(84, 84, 84, 0.70) !important',
fontSize: '20px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '120%', // 24px
letterSpacing: '0.15px',
opacity: 1
},
'&:focus': {
outline: 'none',
},
// Add any additional styles for the input here
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && qortalUrl) {
openQortalUrl();
}
color: qortalUrl
? theme.palette.text.primary
: theme.palette.text.secondary,
}}
/>
<ButtonBase onClick={()=> openQortalUrl()}>
<ArrowOutwardIcon sx={{
color: qortalUrl ? 'white' : 'rgba(84, 84, 84, 0.70)'
}} />
</ButtonBase>
</Box>
</AppsContainer>
</ButtonBase>
</Box>
</AppsContainer>
<Spacer height="45px" />
<AppsContainer
sx={{
gap: "50px",
justifyContent: "flex-start",
gap: '50px',
justifyContent: 'flex-start',
}}
>
<ButtonBase
onClick={() => {
setMode("library");
setMode('library');
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
<Add>+</Add>
<AddIcon />
</AppCircle>
<AppCircleLabel>Library</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<AppsPrivate myName={myName} />
<AppsPrivate myName={myName} />
<SortablePinnedApps
isDesktop={true}
availableQapps={availableQapps}
@@ -145,6 +156,9 @@ export const AppsHomeDesktop = ({
myApp={myApp}
/>
</AppsContainer>
<LanguageSelector />
<ThemeSelector />
</>
);
};

View File

@@ -1,327 +0,0 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
AppsWidthLimiter,
PublishQAppCTAButton,
PublishQAppCTALeft,
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
import qappDots from "../../assets/svgs/qappDots.svg";
import ReturnSVG from '../../assets/svgs/Return.svg'
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles";
const officialAppList = [
'q-tube',
'q-blog',
'q-share',
'q-support',
'q-mail',
'q-fund',
'q-shop',
'q-trade',
'q-support',
'q-manager',
'q-wallets',
'q-search',
"q-nodecontrol"
];
const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => {
const [searchValue, setSearchValue] = useState("");
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
const officialApps = useMemo(() => {
return availableQapps.filter(
(app) =>
app.service === "APP" &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
// Debounce logic
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 350);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
};
}, [searchValue]); // Runs effect when searchValue changes
// Example: Perform search or other actions based on debouncedValue
const searchedList = useMemo(() => {
if (!debouncedValue) return [];
return availableQapps.filter((app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
);
}, [debouncedValue]);
const rowRenderer = (index) => {
let app = searchedList[index];
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} />;
};
return (
<AppsLibraryContainer sx={{
display: !isShow && 'none'
}}>
<AppsWidthLimiter>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "center",
}}
>
<AppsSearchContainer>
<AppsSearchLeft>
<img src={IconSearch} />
<InputBase
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
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>
</Box>
</AppsWidthLimiter>
<Spacer height="25px" />
<ShowMessageReturnButton sx={{
padding: '2px'
}} onClick={() => {
executeEvent("navigateBack", {});
}}>
<MailIconImg src={ReturnSVG} />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<AppsWidthLimiter>
<StyledVirtuosoContainer sx={{
height: rootHeight
}}>
<Virtuoso
ref={virtuosoRef}
data={searchedList}
itemContent={rowRenderer}
atBottomThreshold={50}
followOutput="smooth"
components={{
Scroller: ScrollerStyled // Use the styled scroller component
}}
/>
</StyledVirtuosoContainer>
</AppsWidthLimiter>
) : (
<>
<AppsWidthLimiter>
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsContainer>
{officialApps?.map((qapp) => {
return (
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
onClick={()=> {
// executeEvent("addTab", {
// data: qapp
// })
executeEvent("selectedAppInfo", {
data: qapp,
});
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
qapp?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{qapp?.metadata?.title || qapp?.name}
</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
);
})}
</AppsContainer>
<Spacer height="30px" />
<AppLibrarySubTitle>{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}</AppLibrarySubTitle>
<Spacer height="18px" />
</AppsWidthLimiter>
<PublishQAppCTAParent>
<PublishQAppCTALeft>
<PublishQAppDotsBG>
<img src={qappDots} />
</PublishQAppDotsBG>
<Spacer width="29px" />
<img src={qappDevelopText} />
</PublishQAppCTALeft>
<PublishQAppCTARight onClick={()=> {
setMode('publish')
}}>
<PublishQAppCTAButton>
{hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton>
<Spacer width="20px" />
</PublishQAppCTARight>
</PublishQAppCTAParent>
<AppsWidthLimiter>
<Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsWidthLimiter sx={{
flexDirection: 'row',
overflowX: 'auto',
width: '100%',
gap: '5px',
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
}}>
{categories?.map((category)=> {
return (
<ButtonBase key={category?.id} onClick={()=> {
executeEvent('selectedCategory', {
data: category
})
}}>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '110px',
width: '110px',
background: 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
color: '#1D1D1E',
fontWeight: 700,
fontSize: '16px',
flexShrink: 0,
borderRadius: '11px'
}}>
{category?.name}
</Box>
</ButtonBase>
)
})}
</AppsWidthLimiter>
</AppsWidthLimiter>
</>
)}
</AppsLibraryContainer>
);
};

View File

@@ -1,19 +1,13 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsDesktopLibraryBody,
AppsDesktopLibraryHeader,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
@@ -23,30 +17,31 @@ import {
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, Typography, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
import qappLibraryText from "../../assets/svgs/qappLibraryText.svg";
import RefreshIcon from "@mui/icons-material/Refresh";
import qappDots from "../../assets/svgs/qappDots.svg";
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
} from './Apps-styles';
import {
AppsDesktopLibraryBody,
AppsDesktopLibraryHeader,
} from "./AppsDesktop-styles";
import { AppsNavBarDesktop } from "./AppsNavBarDesktop";
import ReturnSVG from '../../assets/svgs/Return.svg'
import { ComposeP, MailIconImg, ShowMessageReturnButton } from "../Group/Forum/Mail-styles";
Avatar,
Box,
ButtonBase,
InputBase,
Typography,
styled,
useTheme,
} from '@mui/material';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import SearchIcon from '@mui/icons-material/Search';
import IconClearInput from '../../assets/svgs/ClearInput.svg';
import { QappDevelopText } from '../../assets/Icons/QappDevelopText.tsx';
import { QappLibraryText } from '../../assets/Icons/QappLibraryText.tsx';
import RefreshIcon from '@mui/icons-material/Refresh';
import AppsIcon from '@mui/icons-material/Apps';
import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from 'react-virtuoso';
import { executeEvent } from '../../utils/events';
import { ComposeP, ShowMessageReturnButton } from '../Group/Forum/Mail-styles';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon.tsx';
const officialAppList = [
'q-tube',
'q-blog',
@@ -61,40 +56,40 @@ const officialAppList = [
'q-mintership',
'q-wallets',
'q-search',
"q-nodecontrol"
'q-nodecontrol',
];
const ScrollerStyled = styled("div")({
const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
msOverflowStyle: 'none',
});
const StyledVirtuosoContainer = styled("div")({
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
msOverflowStyle: 'none',
});
export const AppsLibraryDesktop = ({
@@ -104,20 +99,21 @@ export const AppsLibraryDesktop = ({
hasPublishApp,
isShow,
categories,
getQapps
getQapps,
}) => {
const [searchValue, setSearchValue] = useState("");
const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef();
const theme = useTheme();
const officialApps = useMemo(() => {
return availableQapps.filter(
(app) =>
app.service === "APP" &&
app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
@@ -125,9 +121,9 @@ export const AppsLibraryDesktop = ({
setDebouncedValue(searchValue);
}, 350);
setTimeout(() => {
virtuosoRef.current.scrollToIndex({
index: 0
});
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({ index: 0 });
}
}, 500);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
@@ -139,8 +135,13 @@ export const AppsLibraryDesktop = ({
const searchedList = useMemo(() => {
if (!debouncedValue) return [];
return availableQapps.filter((app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) || (app?.metadata?.title && app?.metadata?.title?.toLowerCase().includes(debouncedValue.toLowerCase()))
return availableQapps.filter(
(app) =>
app.name.toLowerCase().includes(debouncedValue.toLowerCase()) ||
(app?.metadata?.title &&
app?.metadata?.title
?.toLowerCase()
.includes(debouncedValue.toLowerCase()))
);
}, [debouncedValue]);
@@ -152,7 +153,7 @@ export const AppsLibraryDesktop = ({
app={app}
myName={myName}
parentStyles={{
padding: '0px 10px'
padding: '0px 10px',
}}
/>
);
@@ -161,111 +162,125 @@ export const AppsLibraryDesktop = ({
return (
<AppsLibraryContainer
sx={{
display: !isShow && "none",
padding: "0px",
height: "100vh",
overflow: "hidden",
paddingTop: '30px'
display: !isShow && 'none',
padding: '0px',
height: '100vh',
overflow: 'hidden',
paddingTop: '30px',
}}
>
<AppsDesktopLibraryHeader
sx={{
maxWidth: "1500px",
width: "90%",
maxWidth: '1500px',
width: '90%',
}}
>
<AppsWidthLimiter>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "space-between",
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
<img src={qappLibraryText} />
<Box sx={{
display: 'flex',
gap: '20px',
alignItems: 'center'
}}>
<AppsSearchContainer
<QappLibraryText />
<Box
sx={{
width: "412px",
alignItems: 'center',
display: 'flex',
gap: '20px',
}}
>
<AppsSearchLeft>
<img src={IconSearch} />
<InputBase
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
sx={{ ml: 1, flex: 1 }}
placeholder="Search for apps"
inputProps={{
"aria-label": "Search for apps",
fontSize: "16px",
fontWeight: 400,
<AppsSearchContainer
sx={{
width: '412px',
}}
>
<AppsSearchLeft>
<SearchIcon />
<InputBase
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
sx={{
background: theme.palette.background.paper,
borderRadius: '6px',
flex: 1,
ml: 1,
paddingLeft: '12px',
}}
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={{
width: '40px',
height: 'auto',
}}
/>
</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'
}}
/>
</ButtonBase>
</ButtonBase>
</Box>
</Box>
</AppsWidthLimiter>
</AppsDesktopLibraryHeader>
<AppsDesktopLibraryBody
sx={{
alignItems: 'center',
height: `calc(100vh - 36px)`,
overflow: "auto",
padding: "0px",
alignItems: "center",
overflow: 'auto',
padding: '0px',
}}
>
<AppsDesktopLibraryBody
sx={{
height: `calc(100vh - 36px)`,
flexGrow: "unset",
maxWidth: "1500px",
width: "90%",
flexGrow: 'unset',
maxWidth: '1500px',
width: '90%',
}}
>
<Spacer height="70px" />
<ShowMessageReturnButton sx={{
padding: '2px'
}} onClick={() => {
executeEvent("navigateBack", {});
}}>
<MailIconImg src={ReturnSVG} />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
<Spacer height="20px" />
<ShowMessageReturnButton
sx={{
padding: '2px',
}}
onClick={() => {
executeEvent('navigateBack', {});
}}
>
<ReturnIcon />
<ComposeP>Return to Apps Dashboard</ComposeP>
</ShowMessageReturnButton>
<Spacer height="20px" />
{searchedList?.length > 0 ? (
<AppsWidthLimiter>
<StyledVirtuosoContainer
@@ -293,46 +308,46 @@ export const AppsLibraryDesktop = ({
<>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Official Apps
</AppLibrarySubTitle>
<Spacer height="45px" />
<AppsContainer sx={{
gap: '50px',
justifyContent: 'flex-start'
}}>
<AppsContainer
sx={{
gap: '15px',
justifyContent: 'center',
}}
>
{officialApps?.map((qapp) => {
return (
<ButtonBase
sx={{
// height: "80px",
width: "80px",
width: '80px',
}}
onClick={() => {
// executeEvent("addTab", {
// data: qapp
// })
executeEvent("selectedAppInfo", {
executeEvent('selectedAppInfo', {
data: qapp,
});
}}
>
<AppCircleContainer
sx={{
gap: "10px",
gap: '10px',
}}
>
<AppCircle
sx={{
border: "none",
border: 'none',
}}
>
<Avatar
sx={{
height: "42px",
width: "42px",
height: '42px',
width: '42px',
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@@ -341,14 +356,15 @@ export const AppsLibraryDesktop = ({
>
<img
style={{
width: "31px",
height: "auto",
width: '31px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{qapp?.metadata?.title || qapp?.name}
</AppCircleLabel>
@@ -357,122 +373,141 @@ export const AppsLibraryDesktop = ({
);
})}
</AppsContainer>
<Spacer height="80px" />
<Box
sx={{
width: "100%",
gap: "250px",
display: "flex",
width: '100%',
gap: '250px',
display: 'flex',
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
width: "100%",
textAlign: "start",
fontSize: '30px',
width: '100%',
textAlign: 'start',
}}
>
{hasPublishApp ? "Update Apps!" : "Create Apps!"}
{hasPublishApp ? 'Update your app' : 'Publish your app'}
</AppLibrarySubTitle>
<Spacer height="18px" />
<PublishQAppCTAParent
sx={{
gap: "25px",
gap: '25px',
}}
>
<PublishQAppCTALeft>
<PublishQAppDotsBG>
<img src={qappDots} />
<AppsIcon fontSize="large" />
</PublishQAppDotsBG>
<Spacer width="29px" />
<img src={qappDevelopText} />
<QappDevelopText />
</PublishQAppCTALeft>
<PublishQAppCTARight
onClick={() => {
setMode("publish");
setMode('publish');
}}
>
<PublishQAppCTAButton>
{hasPublishApp ? "Update" : "Publish"}
{hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton>
<Spacer width="20px" />
</PublishQAppCTARight>
</PublishQAppCTAParent>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
}}
>
<AppLibrarySubTitle
sx={{
fontSize: "30px",
fontSize: '30px',
}}
>
Categories
</AppLibrarySubTitle>
<Spacer height="18px" />
<Box
sx={{
width: "100%",
display: "flex",
gap: "20px",
flexWrap: "wrap",
display: 'flex',
flexWrap: 'wrap',
gap: '20px',
width: '100%',
}}
>
<ButtonBase
onClick={() => {
executeEvent("selectedCategory", {
data: {
id: 'all',
name: 'All'
},
});
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "60px",
padding: "0px 24px",
border: "4px solid #10242F",
borderRadius: "6px",
boxShadow: "2px 4px 0px 0px #000000",
}}
>
All
</Box>
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent('selectedCategory', {
data: {
id: 'all',
name: 'All',
},
});
}}
>
<Box
sx={{
alignItems: 'center',
borderColor: theme.palette.background.paper,
borderRadius: '6px',
borderStyle: 'solid',
borderWidth: '4px',
display: 'flex',
height: '50px',
justifyContent: 'center',
padding: '0px 20px',
'&:hover': {
backgroundColor: 'action.hover', // background on hover
},
}}
>
All
</Box>
</ButtonBase>
{categories?.map((category) => {
return (
<ButtonBase
key={category?.id}
onClick={() => {
executeEvent("selectedCategory", {
executeEvent('selectedCategory', {
data: category,
});
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "60px",
padding: "0px 24px",
border: "4px solid #10242F",
borderRadius: "6px",
boxShadow: "2px 4px 0px 0px #000000",
alignItems: 'center',
borderColor: theme.palette.background.paper,
borderRadius: '6px',
borderStyle: 'solid',
borderWidth: '4px',
display: 'flex',
height: '50px',
justifyContent: 'center',
padding: '0px 20px',
'&:hover': {
backgroundColor: 'action.hover', // background on hover
},
}}
>
{category?.name}

View File

@@ -1,353 +0,0 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
AppsNavBarLeft,
AppsNavBarParent,
AppsNavBarRight,
} from "./Apps-styles";
import NavBack from "../../assets/svgs/NavBack.svg";
import NavAdd from "../../assets/svgs/NavAdd.svg";
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
import {
ButtonBase,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Tab,
Tabs,
} from "@mui/material";
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from "../../utils/events";
import TabComponent from "./TabComponent";
import PushPinIcon from "@mui/icons-material/PushPin";
import RefreshIcon from "@mui/icons-material/Refresh";
import { useRecoilState, useSetRecoilState } from "recoil";
import {
navigationControllerAtom,
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from "../../atoms/global";
export function saveToLocalStorage(key, subKey, newValue, otherRootData = {}, deleteWholeKey) {
try {
if(deleteWholeKey){
localStorage.setItem(key, null);
return
}
// Fetch existing data
const existingData = localStorage.getItem(key);
let combinedData = {};
if (existingData) {
// Parse the existing data
const parsedData = JSON.parse(existingData);
// Merge with the new data under the subKey
combinedData = {
...parsedData,
...otherRootData,
timestamp: Date.now(), // Update the root timestamp
[subKey]: newValue, // Assuming the data is an array
};
} else {
// If no existing data, just use the new data under the subKey
combinedData = {
...otherRootData,
timestamp: Date.now(), // Set the initial root timestamp
[subKey]: newValue,
};
}
// Save combined data back to localStorage
const serializedValue = JSON.stringify(combinedData);
localStorage.setItem(key, serializedValue);
} catch (error) {
console.error("Error saving to localStorage:", error);
}
}
export const AppsNavBar = () => {
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
const isDisableBackButton = useMemo(()=> {
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
return false
}, [navigationController, selectedTab])
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
useEffect(() => {
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
if (tabsRef.current) {
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
if (tabElements.length > 0) {
const lastTab = tabElements[tabElements.length - 1];
lastTab.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "end",
});
}
}
}, [tabs.length]); // Dependency on the number of tabs
const setTabsToNav = (e) => {
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
setTabs([...tabs]);
setSelectedTab(!selectedTab ? null : { ...selectedTab });
setIsNewTabWindow(isNewTabWindow);
};
useEffect(() => {
subscribeToEvent("setTabsToNav", setTabsToNav);
return () => {
unsubscribeFromEvent("setTabsToNav", setTabsToNav);
};
}, []);
const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) =>
item?.name === selectedTab?.name && item?.service === selectedTab?.service
);
return (
<AppsNavBarParent>
<AppsNavBarLeft>
<ButtonBase
onClick={() => {
executeEvent("navigateBack", selectedTab?.tabId);
}}
disabled={isDisableBackButton}
sx={{
opacity: !isDisableBackButton ? 1 : 0.1,
cursor: !isDisableBackButton ? 'pointer': 'default'
}}
>
<img src={NavBack} />
</ButtonBase>
<Tabs
ref={tabsRef}
aria-label="basic tabs example"
variant="scrollable" // Make tabs scrollable
scrollButtons={false}
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
maxWidth: `calc(100vw - 150px)`, // Ensure the tabs container fits within the available space
overflow: "hidden", // Prevents overflow on small screens
}}
>
{tabs?.map((tab) => (
<Tab
key={tab.tabId}
label={
<TabComponent
isSelected={
tab?.tabId === selectedTab?.tabId && !isNewTabWindow
}
app={tab}
/>
} // Pass custom component
sx={{
"&.Mui-selected": {
color: "white",
},
padding: "0px",
margin: "0px",
minWidth: "0px",
width: "50px",
}}
/>
))}
</Tabs>
</AppsNavBarLeft>
{selectedTab && (
<AppsNavBarRight
sx={{
gap: "10px",
}}
>
<ButtonBase
onClick={() => {
setSelectedTab(null);
executeEvent("newTabWindow", {});
}}
>
<img
style={{
height: "40px",
width: "40px",
}}
src={NavAdd}
/>
</ButtonBase>
<ButtonBase
onClick={(e) => {
if (!selectedTab) return;
handleClick(e);
}}
>
<img
style={{
height: "34px",
width: "34px",
}}
src={NavMoreMenu}
/>
</ButtonBase>
</AppsNavBarRight>
)}
<Menu
id="navbar-more-mobile"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
slotProps={{
paper: {
sx: {
backgroundColor: "var(--bg-primary)",
color: "#fff",
width: "148px",
borderRadius: "5px",
},
},
}}
sx={{
marginTop: "10px",
}}
>
<MenuItem
onClick={() => {
if (!selectedTab) return;
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) =>
!(
item?.name === selectedTab?.name &&
item?.service === selectedTab?.service
)
);
} else {
// Add the selected app if it is not pinned
updatedApps = [
...prev,
{
name: selectedTab?.name,
service: selectedTab?.service,
},
];
}
saveToLocalStorage(
"ext_saved_settings",
"sortablePinnedApps",
updatedApps
);
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
handleClose();
}}
>
<ListItemIcon
sx={{
minWidth: "24px !important",
marginRight: "5px",
}}
>
<PushPinIcon
height={20}
sx={{
color: isSelectedAppPinned ? "red" : "rgba(250, 250, 250, 0.5)",
}}
/>
</ListItemIcon>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: isSelectedAppPinned ? "red" : "rgba(250, 250, 250, 0.5)",
},
}}
primary={`${isSelectedAppPinned ? "Unpin app" : "Pin app"}`}
/>
</MenuItem>
<MenuItem
onClick={() => {
executeEvent("refreshApp", {
tabId: selectedTab?.tabId,
});
handleClose();
}}
>
<ListItemIcon
sx={{
minWidth: "24px !important",
marginRight: "5px",
}}
>
<RefreshIcon
height={20}
sx={{
color: "rgba(250, 250, 250, 0.5)",
}}
/>
</ListItemIcon>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "12px",
fontWeight: 600,
color: "rgba(250, 250, 250, 0.5)",
},
}}
primary="Refresh"
/>
</MenuItem>
</Menu>
</AppsNavBarParent>
);
};

View File

@@ -1,12 +1,12 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from 'react';
import {
AppsNavBarLeft,
AppsNavBarParent,
AppsNavBarRight,
} from "./Apps-styles";
import NavBack from "../../assets/svgs/NavBack.svg";
import NavAdd from "../../assets/svgs/NavAdd.svg";
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
} from './Apps-styles';
import { NavBack } from '../../assets/Icons/NavBack.tsx';
import { NavAdd } from '../../assets/Icons/NavAdd.tsx';
import { NavMoreMenu } from '../../assets/Icons/NavMoreMenu.tsx';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import {
ButtonBase,
@@ -16,21 +16,22 @@ import {
MenuItem,
Tab,
Tabs,
} from "@mui/material";
useTheme,
} from '@mui/material';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from "../../utils/events";
import TabComponent from "./TabComponent";
import PushPinIcon from "@mui/icons-material/PushPin";
import RefreshIcon from "@mui/icons-material/Refresh";
import { useRecoilState, useSetRecoilState } from "recoil";
} from '../../utils/events';
import TabComponent from './TabComponent';
import PushPinIcon from '@mui/icons-material/PushPin';
import RefreshIcon from '@mui/icons-material/Refresh';
import {
navigationControllerAtom,
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from "../../atoms/global";
} from '../../atoms/global';
import { useAtom, useSetAtom } from 'jotai';
export function saveToLocalStorage(key, subKey, newValue) {
try {
@@ -59,27 +60,28 @@ export function saveToLocalStorage(key, subKey, newValue) {
const serializedValue = JSON.stringify(combinedData);
localStorage.setItem(key, serializedValue);
} catch (error) {
console.error("Error saving to localStorage:", error);
console.error('Error saving to localStorage:', error);
}
}
export const AppsNavBarDesktop = ({disableBack}) => {
export const AppsNavBarDesktop = ({ disableBack }) => {
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom
);
const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom
);
const theme = useTheme();
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
@@ -92,29 +94,26 @@ export const AppsNavBarDesktop = ({disableBack}) => {
useEffect(() => {
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
if (tabsRef.current) {
const tabElements = tabsRef.current.querySelectorAll(".MuiTab-root");
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
if (tabElements.length > 0) {
const lastTab = tabElements[tabElements.length - 1];
lastTab.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "end",
behavior: 'smooth',
block: 'nearest',
inline: 'end',
});
}
}
}, [tabs.length]); // Dependency on the number of tabs
const isDisableBackButton = useMemo(()=> {
if(disableBack) return true
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
return false
}, [navigationController, selectedTab, disableBack])
const isDisableBackButton = useMemo(() => {
if (disableBack) return true;
if (selectedTab && navigationController[selectedTab?.tabId]?.hasBack)
return false;
if (selectedTab && !navigationController[selectedTab?.tabId]?.hasBack)
return true;
return false;
}, [navigationController, selectedTab, disableBack]);
const setTabsToNav = (e) => {
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
@@ -124,57 +123,61 @@ export const AppsNavBarDesktop = ({disableBack}) => {
};
useEffect(() => {
subscribeToEvent("setTabsToNav", setTabsToNav);
subscribeToEvent('setTabsToNav', setTabsToNav);
return () => {
unsubscribeFromEvent("setTabsToNav", setTabsToNav);
unsubscribeFromEvent('setTabsToNav', setTabsToNav);
};
}, []);
const isSelectedAppPinned = useMemo(()=> {
if(selectedTab?.isPrivate){
const isSelectedAppPinned = useMemo(() => {
if (selectedTab?.isPrivate) {
return !!sortablePinnedApps?.find(
(item) =>
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name && item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service && item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
item?.privateAppProperties?.name ===
selectedTab?.privateAppProperties?.name &&
item?.privateAppProperties?.service ===
selectedTab?.privateAppProperties?.service &&
item?.privateAppProperties?.identifier ===
selectedTab?.privateAppProperties?.identifier
);
} else {
return !!sortablePinnedApps?.find(
(item) =>
item?.name === selectedTab?.name && item?.service === selectedTab?.service
item?.name === selectedTab?.name &&
item?.service === selectedTab?.service
);
}
}, [selectedTab,sortablePinnedApps])
}, [selectedTab, sortablePinnedApps]);
return (
<AppsNavBarParent
sx={{
position: "relative",
flexDirection: "column",
width: "60px",
height: "unset",
maxHeight: "70vh",
borderRadius: "0px 30px 30px 0px",
padding: "10px",
borderRadius: '0px 30px 30px 0px',
flexDirection: 'column',
height: 'unset',
maxHeight: '70vh',
padding: '10px',
position: 'relative',
width: '59px',
}}
>
<AppsNavBarLeft
sx={{
flexDirection: "column",
flexDirection: 'column',
}}
>
<ButtonBase
onClick={() => {
executeEvent("navigateBack", selectedTab?.tabId);
executeEvent('navigateBack', selectedTab?.tabId);
}}
disabled={isDisableBackButton}
sx={{
opacity: !isDisableBackButton ? 1 : 0.1,
cursor: !isDisableBackButton ? 'pointer': 'default'
cursor: !isDisableBackButton ? 'pointer' : 'default',
}}
>
<img src={NavBack} />
<NavBack />
</ButtonBase>
<Tabs
orientation="vertical"
@@ -183,11 +186,11 @@ export const AppsNavBarDesktop = ({disableBack}) => {
variant="scrollable" // Make tabs scrollable
scrollButtons={true}
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.background.default,
},
maxHeight: `320px`, // Ensure the tabs container fits within the available space
overflow: "hidden", // Prevents overflow on small screens
maxHeight: `275px`, // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
}}
>
{tabs?.map((tab) => (
@@ -202,84 +205,83 @@ export const AppsNavBarDesktop = ({disableBack}) => {
/>
} // Pass custom component
sx={{
"&.Mui-selected": {
color: "white",
'&.Mui-selected': {
color: theme.palette.text.primary,
},
padding: "0px",
margin: "0px",
minWidth: "0px",
width: "50px",
padding: '0px',
margin: '0px',
minWidth: '0px',
width: '50px',
}}
/>
))}
</Tabs>
</AppsNavBarLeft>
{selectedTab && (
<AppsNavBarRight
sx={{
gap: "10px",
flexDirection: "column",
}}
>
<ButtonBase
onClick={() => {
setSelectedTab(null);
executeEvent("newTabWindow", {});
sx={{
gap: '10px',
flexDirection: 'column',
}}
>
<img
style={{
height: "40px",
width: "40px",
<ButtonBase
onClick={() => {
setSelectedTab(null);
executeEvent('newTabWindow', {});
}}
src={NavAdd}
/>
</ButtonBase>
<ButtonBase
onClick={(e) => {
if (!selectedTab) return;
handleClick(e);
}}
>
<img
style={{
height: "34px",
width: "34px",
>
<NavAdd
style={{
height: '40px',
width: '40px',
}}
/>
</ButtonBase>
<ButtonBase
onClick={(e) => {
if (!selectedTab) return;
handleClick(e);
}}
src={NavMoreMenu}
/>
</ButtonBase>
</AppsNavBarRight>
>
<NavMoreMenu
style={{
height: '34px',
width: '34px',
}}
/>
</ButtonBase>
</AppsNavBarRight>
)}
<Menu
id="navbar-more-mobile"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
'aria-labelledby': 'basic-button',
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
vertical: 'top',
horizontal: 'center',
}}
slotProps={{
paper: {
sx: {
backgroundColor: "var(--bg-primary)",
color: "#fff",
width: "148px",
borderRadius: "5px",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
width: '148px',
borderRadius: '5px',
},
},
}}
sx={{
marginTop: "10px",
marginTop: '10px',
}}
>
<MenuItem
@@ -291,13 +293,16 @@ export const AppsNavBarDesktop = ({disableBack}) => {
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
if(selectedTab?.isPrivate){
if (selectedTab?.isPrivate) {
updatedApps = prev.filter(
(item) =>
!(
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name &&
item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service &&
item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
item?.privateAppProperties?.name ===
selectedTab?.privateAppProperties?.name &&
item?.privateAppProperties?.service ===
selectedTab?.privateAppProperties?.service &&
item?.privateAppProperties?.identifier ===
selectedTab?.privateAppProperties?.identifier
)
);
} else {
@@ -309,21 +314,19 @@ export const AppsNavBarDesktop = ({disableBack}) => {
)
);
}
} else {
// Add the selected app if it is not pinned
if(selectedTab?.isPrivate){
if (selectedTab?.isPrivate) {
updatedApps = [
...prev,
{
isPreview: true,
isPrivate: true,
privateAppProperties: {
...(selectedTab?.privateAppProperties || {})
}
},
];
...prev,
{
isPreview: true,
isPrivate: true,
privateAppProperties: {
...(selectedTab?.privateAppProperties || {}),
},
},
];
} else {
updatedApps = [
...prev,
@@ -333,12 +336,11 @@ export const AppsNavBarDesktop = ({disableBack}) => {
},
];
}
}
saveToLocalStorage(
"ext_saved_settings",
"sortablePinnedApps",
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
);
return updatedApps;
@@ -350,70 +352,74 @@ export const AppsNavBarDesktop = ({disableBack}) => {
>
<ListItemIcon
sx={{
minWidth: "24px !important",
marginRight: "5px",
minWidth: '24px !important',
marginRight: '5px',
}}
>
<PushPinIcon
height={20}
sx={{
color: isSelectedAppPinned ? "var(--danger)" : "rgba(250, 250, 250, 0.5)",
color: isSelectedAppPinned
? theme.palette.other.danger
: theme.palette.text.primary,
}}
/>
</ListItemIcon>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "12px",
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
color: isSelectedAppPinned ? "var(--danger)" : "rgba(250, 250, 250, 0.5)",
color: isSelectedAppPinned
? theme.palette.other.danger
: theme.palette.text.primary,
},
}}
primary={`${isSelectedAppPinned ? "Unpin app" : "Pin app"}`}
primary={`${isSelectedAppPinned ? 'Unpin app' : 'Pin app'}`}
/>
</MenuItem>
<MenuItem
onClick={() => {
if (selectedTab?.refreshFunc) {
selectedTab.refreshFunc(selectedTab?.tabId);
} else {
executeEvent("refreshApp", {
executeEvent('refreshApp', {
tabId: selectedTab?.tabId,
});
}
handleClose();
}}
>
<ListItemIcon
sx={{
minWidth: "24px !important",
marginRight: "5px",
minWidth: '24px !important',
marginRight: '5px',
}}
>
<RefreshIcon
height={20}
sx={{
color: "rgba(250, 250, 250, 0.5)",
color: theme.palette.text.primary,
}}
/>
</ListItemIcon>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "12px",
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
color: "rgba(250, 250, 250, 0.5)",
color: theme.palette.text.primary,
},
}}
primary="Refresh"
/>
</MenuItem>
{!selectedTab?.isPrivate && (
<MenuItem
<MenuItem
onClick={() => {
executeEvent("copyLink", {
executeEvent('copyLink', {
tabId: selectedTab?.tabId,
});
handleClose();
@@ -421,23 +427,24 @@ export const AppsNavBarDesktop = ({disableBack}) => {
>
<ListItemIcon
sx={{
minWidth: "24px !important",
marginRight: "5px",
minWidth: '24px !important',
marginRight: '5px',
}}
>
<ContentCopyIcon
height={20}
sx={{
color: "rgba(250, 250, 250, 0.5)",
color: theme.palette.text.primary,
}}
/>
</ListItemIcon>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "12px",
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
color: "rgba(250, 250, 250, 0.5)",
color: theme.palette.text.primary,
},
}}
primary="Copy link"

View File

@@ -1,79 +1,90 @@
import React, { useContext, useMemo, useState } from "react";
import React, { useContext, useMemo, useState } from 'react';
import {
Avatar,
Box,
Button,
ButtonBase,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Input,
MenuItem,
Select,
Tab,
Tabs,
Typography,
} from "@mui/material";
import { useDropzone } from "react-dropzone";
import { useHandlePrivateApps } from "./useHandlePrivateApps";
import { useRecoilState, useSetRecoilState } from "recoil";
import { groupsPropertiesAtom, myGroupsWhereIAmAdminAtom } from "../../atoms/global";
import { Label } from "../Group/AddGroup";
import { Spacer } from "../../common/Spacer";
useTheme,
} from '@mui/material';
import { useDropzone } from 'react-dropzone';
import { useHandlePrivateApps } from './useHandlePrivateApps';
import {
groupsPropertiesAtom,
memberGroupsAtom,
myGroupsWhereIAmAdminAtom,
} from '../../atoms/global';
import { Label } from '../Group/AddGroup';
import { Spacer } from '../../common/Spacer';
import {
Add,
AppCircle,
AppCircleContainer,
AppCircleLabel,
PublishQAppChoseFile,
PublishQAppInfo,
} from "./Apps-styles";
import ImageUploader from "../../common/ImageUploader";
import { isMobile, MyContext } from "../../App";
import { fileToBase64 } from "../../utils/fileReading";
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
import { getFee } from "../../background";
} from './Apps-styles';
import AddIcon from '@mui/icons-material/Add';
import ImageUploader from '../../common/ImageUploader';
import { MyContext } from '../../App';
import { fileToBase64 } from '../../utils/fileReading';
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import { getFee } from '../../background';
import { useAtom } from 'jotai';
const maxFileSize = 50 * 1024 * 1024; // 50MB
export const AppsPrivate = ({myName}) => {
export const AppsPrivate = ({ myName }) => {
const { openApp } = useHandlePrivateApps();
const [file, setFile] = useState(null);
const [logo, setLogo] = useState(null);
const [qortalUrl, setQortalUrl] = useState("");
const [qortalUrl, setQortalUrl] = useState('');
const [selectedGroup, setSelectedGroup] = useState(0);
const [groupsProperties] = useRecoilState(groupsPropertiesAtom)
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
myGroupsWhereIAmAdminAtom
);
const [groupsProperties] = useAtom(groupsPropertiesAtom);
const [myGroupsWhereIAmAdminFromGlobal] = useAtom(myGroupsWhereIAmAdminAtom);
const myGroupsWhereIAmAdmin = useMemo(() => {
return myGroupsWhereIAmAdminFromGlobal?.filter(
(group) => groupsProperties[group?.groupId]?.isOpen === false
);
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties]);
const myGroupsWhereIAmAdmin = useMemo(()=> {
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext);
const { show, setInfoSnackCustom, setOpenSnackGlobal } =
useContext(MyContext);
const [memberGroups] = useAtom(memberGroupsAtom);
const theme = useTheme();
const myGroupsPrivate = useMemo(() => {
return memberGroups?.filter(
(group) => groupsProperties[group?.groupId]?.isOpen === false
);
}, [memberGroups, groupsProperties]);
const myGroupsPrivate = useMemo(()=> {
return memberGroups?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
}, [memberGroups, groupsProperties])
const [privateAppValues, setPrivateAppValues] = useState({
name: "",
service: "DOCUMENT",
identifier: "",
name: '',
service: 'DOCUMENT',
identifier: '',
groupId: 0,
});
const [newPrivateAppValues, setNewPrivateAppValues] = useState({
service: "DOCUMENT",
identifier: "",
name: "",
service: 'DOCUMENT',
identifier: '',
name: '',
});
const { getRootProps, getInputProps } = useDropzone({
accept: {
"application/zip": [".zip"], // Only accept zip files
'application/zip': ['.zip'], // Only accept zip files
},
maxSize: maxFileSize,
multiple: false, // Disable multiple file uploads
@@ -85,7 +96,7 @@ export const AppsPrivate = ({myName}) => {
onDropRejected: (fileRejections) => {
fileRejections.forEach(({ file, errors }) => {
errors.forEach((error) => {
if (error.code === "file-too-large") {
if (error.code === 'file-too-large') {
console.error(
`File ${file.name} is too large. Max size allowed is ${
maxFileSize / (1024 * 1024)
@@ -100,25 +111,24 @@ export const AppsPrivate = ({myName}) => {
const addPrivateApp = async () => {
try {
if (privateAppValues?.groupId === 0) return;
await openApp(privateAppValues, true);
await openApp(privateAppValues, true);
} catch (error) {
console.error(error)
console.error(error);
}
};
const clearFields = () => {
setPrivateAppValues({
name: "",
service: "DOCUMENT",
identifier: "",
name: '',
service: 'DOCUMENT',
identifier: '',
groupId: 0,
});
setNewPrivateAppValues({
service: "DOCUMENT",
identifier: "",
name: "",
service: 'DOCUMENT',
identifier: '',
name: '',
});
setFile(null);
setValueTabPrivateApp(0);
@@ -129,9 +139,9 @@ export const AppsPrivate = ({myName}) => {
const publishPrivateApp = async () => {
try {
if (selectedGroup === 0) return;
if (!logo) throw new Error("Please select an image for a logo");
if (!myName) throw new Error("You need a Qortal name to publish");
if (!newPrivateAppValues?.name) throw new Error("Your app needs a name");
if (!logo) throw new Error('Please select an image for a logo');
if (!myName) throw new Error('You need a Qortal name to publish');
if (!newPrivateAppValues?.name) throw new Error('Your app needs a name');
const base64Logo = await fileToBase64(logo);
const base64App = await fileToBase64(file);
const objectToSave = {
@@ -141,27 +151,29 @@ export const AppsPrivate = ({myName}) => {
};
const object64 = await objectToBase64(objectToSave);
const decryptedData = await window.sendMessage(
"ENCRYPT_QORTAL_GROUP_DATA",
'ENCRYPT_QORTAL_GROUP_DATA',
{
base64: object64,
groupId: selectedGroup,
}
);
if (decryptedData?.error) {
throw new Error(
decryptedData?.error || "Unable to encrypt app. App not published"
decryptedData?.error || 'Unable to encrypt app. App not published'
);
}
const fee = await getFee("ARBITRARY");
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to publish this app?",
publishFee: fee.fee + " QORT",
message: 'Would you like to publish this app?',
publishFee: fee.fee + ' QORT',
});
await new Promise((res, rej) => {
window
.sendMessage("publishOnQDN", {
.sendMessage('publishOnQDN', {
data: decryptedData,
identifier: newPrivateAppValues?.identifier,
service: newPrivateAppValues?.service,
@@ -174,9 +186,10 @@ export const AppsPrivate = ({myName}) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
openApp(
{
identifier: newPrivateAppValues?.identifier,
@@ -188,10 +201,10 @@ export const AppsPrivate = ({myName}) => {
);
clearFields();
} catch (error) {
setOpenSnackGlobal(true)
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to publish app",
type: 'error',
message: error?.message || 'Unable to publish app',
});
}
};
@@ -203,9 +216,10 @@ export const AppsPrivate = ({myName}) => {
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
return (
<>
<ButtonBase
@@ -213,17 +227,18 @@ export const AppsPrivate = ({myName}) => {
setIsOpenPrivateModal(true);
}}
sx={{
width: "80px",
width: '80px',
}}
>
<AppCircleContainer
sx={{
gap: !isMobile ? "10px" : "5px",
gap: '10px',
}}
>
<AppCircle>
<Add>+</Add>
<AddIcon />
</AppCircle>
<AppCircleLabel>Private</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
@@ -233,7 +248,7 @@ export const AppsPrivate = ({myName}) => {
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
onKeyDown={(e) => {
if (e.key === "Enter") {
if (e.key === 'Enter') {
if (valueTabPrivateApp === 0) {
if (
!privateAppValues.name ||
@@ -248,24 +263,23 @@ export const AppsPrivate = ({myName}) => {
}}
maxWidth="md"
fullWidth={true}
PaperProps={{
style: {
backgroundColor: theme.palette.background.paper,
boxShadow: 'none',
},
}}
>
<DialogTitle id="alert-dialog-title">
{valueTabPrivateApp === 0
? "Access private app"
: "Publish private app"}
</DialogTitle>
<Box>
<Tabs
value={valueTabPrivateApp}
onChange={handleChange}
aria-label="basic tabs example"
variant={isMobile ? "scrollable" : "fullWidth"} // Scrollable on mobile, full width on desktop
variant={'fullWidth'}
scrollButtons="auto"
allowScrollButtonsMobile
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.background.default,
},
}}
>
@@ -273,20 +287,20 @@ export const AppsPrivate = ({myName}) => {
label="Access app"
{...a11yProps(0)}
sx={{
"&.Mui-selected": {
color: "white",
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
fontSize: '1rem',
}}
/>
<Tab
label="Publish app"
{...a11yProps(1)}
sx={{
"&.Mui-selected": {
color: "white",
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
fontSize: '1rem',
}}
/>
</Tabs>
@@ -296,9 +310,9 @@ export const AppsPrivate = ({myName}) => {
<DialogContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Select a group</Label>
@@ -333,10 +347,10 @@ export const AppsPrivate = ({myName}) => {
<Spacer height="10px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>name</Label>
@@ -355,10 +369,10 @@ export const AppsPrivate = ({myName}) => {
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>identifier</Label>
@@ -376,6 +390,7 @@ export const AppsPrivate = ({myName}) => {
/>
</Box>
</DialogContent>
<DialogActions>
<Button
variant="contained"
@@ -406,15 +421,19 @@ export const AppsPrivate = ({myName}) => {
<DialogContent>
<PublishQAppInfo
sx={{
fontSize: "14px",
backgroundColor: theme.palette.background.paper,
fontSize: '14px',
}}
>
Select .zip file containing static content:{" "}
Select .zip file containing static content:{' '}
</PublishQAppInfo>
<Spacer height="10px" />
<PublishQAppInfo
sx={{
fontSize: "14px",
backgroundColor: theme.palette.background.paper,
fontSize: '14px',
}}
>{`
50mb MB maximum`}</PublishQAppInfo>
@@ -426,17 +445,26 @@ export const AppsPrivate = ({myName}) => {
)}
<Spacer height="18px" />
<PublishQAppChoseFile {...getRootProps()}>
{" "}
<PublishQAppChoseFile
sx={{
backgroundColor: theme.palette.background.default,
fontSize: '14px',
}}
{...getRootProps()}
>
{' '}
<input {...getInputProps()} />
{file ? "Change" : "Choose"} File
{file ? 'Change' : 'Choose'} File
</PublishQAppChoseFile>
<Spacer height="20px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Select a group</Label>
@@ -462,14 +490,15 @@ export const AppsPrivate = ({myName}) => {
})}
</Select>
</Box>
<Spacer height="20px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>identifier</Label>
@@ -486,13 +515,15 @@ export const AppsPrivate = ({myName}) => {
}
/>
</Box>
<Spacer height="10px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "5px",
marginTop: "15px",
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '15px',
}}
>
<Label>App name</Label>
@@ -511,12 +542,15 @@ export const AppsPrivate = ({myName}) => {
</Box>
<Spacer height="10px" />
<ImageUploader onPick={(file) => setLogo(file)}>
<Button variant="contained">Choose logo</Button>
</ImageUploader>
{logo?.name}
<Spacer height="25px" />
</DialogContent>
<DialogActions>
<Button
variant="contained"
@@ -527,6 +561,7 @@ export const AppsPrivate = ({myName}) => {
>
Close
</Button>
<Button
disabled={
!newPrivateAppValues.name ||

View File

@@ -1,205 +1,249 @@
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useMemo } from 'react';
import { DndContext, closestCenter } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable } from '@dnd-kit/sortable';
import { KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
} from '@dnd-kit/sortable';
import {
KeyboardSensor,
PointerSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { CSS } from '@dnd-kit/utilities';
import { Avatar, ButtonBase } from '@mui/material';
import { AppCircle, AppCircleContainer, AppCircleLabel } from './Apps-styles';
import { getBaseApiReact, MyContext } from '../../App';
import { getBaseApiReact } from '../../App';
import { executeEvent } from '../../utils/events';
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { saveToLocalStorage } from './AppsNavBar';
import {
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop';
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
import LockIcon from "@mui/icons-material/Lock";
import LockIcon from '@mui/icons-material/Lock';
import { useHandlePrivateApps } from './useHandlePrivateApps';
import { useAtom, useSetAtom } from 'jotai';
const SortableItem = ({ id, name, app, isDesktop }) => {
const {openApp} = useHandlePrivateApps()
const { openApp } = useHandlePrivateApps();
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
padding: '10px',
border: '1px solid #ccc',
marginBottom: '5px',
borderRadius: '4px',
backgroundColor: '#f9f9f9',
cursor: 'grab',
color: 'black'
};
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id });
return (
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
<ButtonBase
ref={setNodeRef} {...attributes} {...listeners}
sx={{
width: "80px",
transform: CSS.Transform.toString(transform),
transition,
}}
onClick={async ()=> {
if(app?.isPrivate){
try {
await openApp(app?.privateAppProperties)
} catch (error) {
console.error(error)
}
} else {
executeEvent("addTab", {
data: app
})
}
}}
>
<AppCircleContainer sx={{
border: "none",
gap: isDesktop ? '10px': '5px'
}}>
<AppCircle
sx={{
border: "none",
}}
>
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
<LockIcon
sx={{
height: "42px",
width: "42px",
}}
/>
) : (
<Avatar
sx={{
height: "42px",
width: "42px",
'& img': {
objectFit: 'fill',
}
}}
alt={app?.metadata?.title || app?.name}
src={ app?.privateAppProperties?.logo ? app?.privateAppProperties?.logo :`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
// src={LogoSelected}
alt="center-icon"
/>
</Avatar>
)}
</AppCircle>
{app?.isPrivate ? (
<AppCircleLabel>
{`${app?.privateAppProperties?.appName || "Private"}`}
</AppCircleLabel>
) : (
<AppCircleLabel>
{app?.metadata?.title || app?.name}
</AppCircleLabel>
)}
</AppCircleContainer>
</ButtonBase>
</ContextMenuPinnedApps>
);
};
const style = {
backgroundColor: '#f9f9f9',
border: '1px solid #ccc',
borderRadius: '4px',
color: 'black',
cursor: 'grab',
marginBottom: '5px',
padding: '10px',
transform: CSS.Transform.toString(transform),
transition,
};
export const SortablePinnedApps = ({ isDesktop, myWebsite, myApp, availableQapps = [] }) => {
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
const transformPinnedApps = useMemo(() => {
// Clone the existing pinned apps list
let pinned = [...pinnedApps];
// Function to add or update `isMine` property
const addOrUpdateIsMine = (pinnedList, appToCheck) => {
if (!appToCheck) return pinnedList;
const existingIndex = pinnedList.findIndex(
(item) => item?.service === appToCheck?.service && item?.name === appToCheck?.name
);
if (existingIndex !== -1) {
// If the app is already in the list, update it with `isMine: true`
pinnedList[existingIndex] = { ...pinnedList[existingIndex], isMine: true };
return (
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
<ButtonBase
ref={setNodeRef}
{...attributes}
{...listeners}
sx={{
width: '80px',
transform: CSS.Transform.toString(transform),
transition,
}}
onClick={async () => {
if (app?.isPrivate) {
try {
await openApp(app?.privateAppProperties);
} catch (error) {
console.error(error);
}
} else {
// If not in the list, add it with `isMine: true` at the beginning
pinnedList.unshift({ ...appToCheck, isMine: true });
executeEvent('addTab', {
data: app,
});
}
return pinnedList;
};
// Update or add `myWebsite` and `myApp` while preserving their positions
pinned = addOrUpdateIsMine(pinned, myWebsite);
pinned = addOrUpdateIsMine(pinned, myApp);
// Update pinned list based on availableQapps
pinned = pinned.map((pin) => {
const findIndex = availableQapps?.findIndex(
(item) => item?.service === pin?.service && item?.name === pin?.name
);
if (findIndex !== -1) return {
...availableQapps[findIndex],
...pin
}
return pin;
});
return pinned;
}, [myApp, myWebsite, pinnedApps, availableQapps]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 10, // Set a distance to avoid triggering drag on small movements
},
}),
useSensor(TouchSensor, {
activationConstraint: {
distance: 10, // Also apply to touch
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragEnd = (event) => {
const { active, over } = event;
if (!over) return; // Make sure the drop target exists
if (active.id !== over.id) {
const oldIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === active.id);
const newIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === over.id);
const newOrder = arrayMove(transformPinnedApps, oldIndex, newIndex);
setPinnedApps(newOrder);
saveToLocalStorage('ext_saved_settings','sortablePinnedApps', newOrder)
setSettingsLocalLastUpdated(Date.now())
}
};
return (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={transformPinnedApps.map((app) => `${app?.service}-${app?.name}`)}>
{transformPinnedApps.map((app) => (
<SortableItem isDesktop={isDesktop} key={`${app?.service}-${app?.name}`} id={`${app?.service}-${app?.name}`} name={app?.name} app={app} />
))}
</SortableContext>
</DndContext>
);
}}
>
<AppCircleContainer
sx={{
border: 'none',
gap: isDesktop ? '10px' : '5px',
}}
>
<AppCircle
sx={{
border: 'none',
}}
>
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
<LockIcon
sx={{
height: '42px',
width: '42px',
}}
/>
) : (
<Avatar
sx={{
height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt={app?.metadata?.title || app?.name}
src={
app?.privateAppProperties?.logo
? app?.privateAppProperties?.logo
: `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`
}
>
<img
style={{
width: '31px',
height: 'auto',
}}
// src={LogoSelected}
alt="center-icon"
/>
</Avatar>
)}
</AppCircle>
{app?.isPrivate ? (
<AppCircleLabel>
{`${app?.privateAppProperties?.appName || 'Private'}`}
</AppCircleLabel>
) : (
<AppCircleLabel>{app?.metadata?.title || app?.name}</AppCircleLabel>
)}
</AppCircleContainer>
</ButtonBase>
</ContextMenuPinnedApps>
);
};
export const SortablePinnedApps = ({
isDesktop,
myWebsite,
myApp,
availableQapps = [],
}) => {
const [pinnedApps, setPinnedApps] = useAtom(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const transformPinnedApps = useMemo(() => {
// Clone the existing pinned apps list
let pinned = [...pinnedApps];
// Function to add or update `isMine` property
const addOrUpdateIsMine = (pinnedList, appToCheck) => {
if (!appToCheck) return pinnedList;
const existingIndex = pinnedList.findIndex(
(item) =>
item?.service === appToCheck?.service &&
item?.name === appToCheck?.name
);
if (existingIndex !== -1) {
// If the app is already in the list, update it with `isMine: true`
pinnedList[existingIndex] = {
...pinnedList[existingIndex],
isMine: true,
};
} else {
// If not in the list, add it with `isMine: true` at the beginning
pinnedList.unshift({ ...appToCheck, isMine: true });
}
return pinnedList;
};
// Update or add `myWebsite` and `myApp` while preserving their positions
pinned = addOrUpdateIsMine(pinned, myWebsite);
pinned = addOrUpdateIsMine(pinned, myApp);
// Update pinned list based on availableQapps
pinned = pinned.map((pin) => {
const findIndex = availableQapps?.findIndex(
(item) => item?.service === pin?.service && item?.name === pin?.name
);
if (findIndex !== -1)
return {
...availableQapps[findIndex],
...pin,
};
return pin;
});
return pinned;
}, [myApp, myWebsite, pinnedApps, availableQapps]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 10, // Set a distance to avoid triggering drag on small movements
},
}),
useSensor(TouchSensor, {
activationConstraint: {
distance: 10, // Also apply to touch
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragEnd = (event) => {
const { active, over } = event;
if (!over) return; // Make sure the drop target exists
if (active.id !== over.id) {
const oldIndex = transformPinnedApps.findIndex(
(item) => `${item?.service}-${item?.name}` === active.id
);
const newIndex = transformPinnedApps.findIndex(
(item) => `${item?.service}-${item?.name}` === over.id
);
const newOrder = arrayMove(transformPinnedApps, oldIndex, newIndex);
setPinnedApps(newOrder);
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', newOrder);
setSettingsLocalLastUpdated(Date.now());
}
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={transformPinnedApps.map((app) => `${app?.service}-${app?.name}`)}
>
{transformPinnedApps.map((app) => (
<SortableItem
isDesktop={isDesktop}
key={`${app?.service}-${app?.name}`}
id={`${app?.service}-${app?.name}`}
name={app?.name}
app={app}
/>
))}
</SortableContext>
</DndContext>
);
};

View File

@@ -1,71 +1,80 @@
import React from 'react'
import { TabParent } from './Apps-styles'
import NavCloseTab from "../../assets/svgs/NavCloseTab.svg";
import { TabParent } from './Apps-styles';
import { NavCloseTab } from '../../assets/Icons/NavCloseTab.tsx';
import { getBaseApiReact } from '../../App';
import { Avatar, ButtonBase } from '@mui/material';
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Avatar, ButtonBase, useTheme } from '@mui/material';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import LockIcon from "@mui/icons-material/Lock";
import LockIcon from '@mui/icons-material/Lock';
const TabComponent = ({ isSelected, app }) => {
const theme = useTheme();
const TabComponent = ({isSelected, app}) => {
return (
<ButtonBase onClick={()=> {
if(isSelected){
executeEvent('removeTab', {
data: app
})
return
<ButtonBase
onClick={() => {
if (isSelected) {
executeEvent('removeTab', {
data: app,
});
return;
}
executeEvent('setSelectedTab', {
data: app
})
}}>
<TabParent sx={{
border: isSelected && '1px solid #FFFFFF'
}}>
data: app,
});
}}
>
<TabParent
sx={{
borderStyle: isSelected && 'solid',
borderWidth: isSelected && '1px',
borderColor: isSelected && theme.palette.text.primary,
}}
>
{isSelected && (
<img style={
{
position: 'absolute',
top: '-5px',
right: '-5px',
zIndex: 1
}
} src={NavCloseTab}/>
) }
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
<NavCloseTab
style={{
position: 'absolute',
top: '-5px',
right: '-5px',
zIndex: 1,
}}
/>
)}
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
<LockIcon
sx={{
height: "28px",
width: "28px",
height: '28px',
width: '28px',
}}
/>
) : (
<Avatar
sx={{
height: "28px",
width: "28px",
height: '28px',
width: '28px',
}}
alt={app?.name}
src={app?.privateAppProperties?.logo ? app?.privateAppProperties?.logo :`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
src={
app?.privateAppProperties?.logo
? app?.privateAppProperties?.logo
: `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`
}
>
<img
style={{
width: "28px",
height: "auto",
width: '28px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
)}
</TabParent>
</TabParent>
</ButtonBase>
)
}
);
};
export default TabComponent
export default TabComponent;

View File

@@ -1,49 +1,44 @@
import React, { useContext, useState } from "react";
import { executeEvent } from "../../utils/events";
import { getBaseApiReact, MyContext } from "../../App";
import { createEndpoint } from "../../background";
import { useRecoilState, useSetRecoilState } from "recoil";
import React, { useContext, useState } from 'react';
import { executeEvent } from '../../utils/events';
import { getBaseApiReact, MyContext } from '../../App';
import { createEndpoint } from '../../background';
import {
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from "../../atoms/global";
import { saveToLocalStorage } from "./AppsNavBarDesktop";
import { base64ToBlobUrl } from "../../utils/fileReading";
import { base64ToUint8Array } from "../../qdn/encryption/group-encryption";
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption";
} from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop';
import { base64ToBlobUrl } from '../../utils/fileReading';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { useAtom, useSetAtom } from 'jotai';
export const useHandlePrivateApps = () => {
const [status, setStatus] = useState("");
const [status, setStatus] = useState('');
const {
openSnackGlobal,
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom,
} = useContext(MyContext);
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const openApp = async (
privateAppProperties,
addToPinnedApps,
setLoadingStatePrivateApp
) => {
try {
if(setLoadingStatePrivateApp){
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(`Downloading and decrypting private app.`);
}
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "info",
message: "Fetching app data",
duration: null
type: 'info',
message: 'Fetching app data',
duration: null,
});
const urlData = `${getBaseApiReact()}/arbitrary/${
privateAppProperties?.service
@@ -53,32 +48,30 @@ export const useHandlePrivateApps = () => {
let data;
try {
const responseData = await fetch(urlData, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if(!responseData?.ok){
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp("Error! Unable to download private app.");
if (!responseData?.ok) {
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.');
}
throw new Error("Unable to fetch app");
}
throw new Error('Unable to fetch app');
}
data = await responseData.text();
if (data?.error) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp("Error! Unable to download private app.");
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.');
}
throw new Error("Unable to fetch app");
throw new Error('Unable to fetch app');
}
} catch (error) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp("Error! Unable to download private app.");
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.');
}
throw error;
}
@@ -87,7 +80,7 @@ export const useHandlePrivateApps = () => {
// eslint-disable-next-line no-useless-catch
try {
decryptedData = await window.sendMessage(
"DECRYPT_QORTAL_GROUP_DATA",
'DECRYPT_QORTAL_GROUP_DATA',
{
base64: data,
@@ -95,16 +88,14 @@ export const useHandlePrivateApps = () => {
}
);
if (decryptedData?.error) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.');
}
throw new Error(decryptedData?.error);
}
} catch (error) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.');
}
throw error;
}
@@ -112,19 +103,19 @@ export const useHandlePrivateApps = () => {
try {
const convertToUint = base64ToUint8Array(decryptedData);
const UintToObject = uint8ArrayToObject(convertToUint);
if (decryptedData) {
setInfoSnackCustom({
type: "info",
message: "Building app",
type: 'info',
message: 'Building app',
});
const endpoint = await createEndpoint(
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: UintToObject?.app,
});
@@ -135,7 +126,7 @@ export const useHandlePrivateApps = () => {
);
const res = await fetch(checkIfPreviewLinkStillWorksUrl);
if (res.ok) {
executeEvent("refreshApp", {
executeEvent('refreshApp', {
tabId: tabId,
});
} else {
@@ -143,51 +134,50 @@ export const useHandlePrivateApps = () => {
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
);
const response = await fetch(endpoint, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "text/plain",
'Content-Type': 'text/plain',
},
body: UintToObject?.app,
});
const previewPath = await response.text();
executeEvent("updateAppUrl", {
executeEvent('updateAppUrl', {
tabId: tabId,
url: await createEndpoint(previewPath),
});
setTimeout(() => {
executeEvent("refreshApp", {
executeEvent('refreshApp', {
tabId: tabId,
});
}, 300);
}
};
const appName = UintToObject?.name;
const logo = UintToObject?.logo
? `data:image/png;base64,${UintToObject?.logo}`
: null;
const dataBody = {
url: await createEndpoint(previewPath),
isPreview: true,
isPrivate: true,
privateAppProperties: { ...privateAppProperties, logo, appName },
filePath: "",
filePath: '',
refreshFunc: (tabId) => {
refreshfunc(tabId, privateAppProperties);
},
};
executeEvent("addTab", {
executeEvent('addTab', {
data: dataBody,
});
setInfoSnackCustom({
type: "success",
message: "Opened",
type: 'success',
message: 'Opened',
});
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp(``);
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(``);
}
if (addToPinnedApps) {
setSortablePinnedApps((prev) => {
@@ -203,10 +193,10 @@ export const useHandlePrivateApps = () => {
},
},
];
saveToLocalStorage(
"ext_saved_settings",
"sortablePinnedApps",
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
);
return updatedApps;
@@ -215,20 +205,19 @@ export const useHandlePrivateApps = () => {
}
}
} catch (error) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp(`Error! ${error?.message || 'Unable to build private app.'}`);
if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(
`Error! ${error?.message || 'Unable to build private app.'}`
);
}
throw error
throw error;
}
} catch (error) {
setInfoSnackCustom({
type: 'error',
message: error?.message || 'Unable to fetch app',
});
}
catch (error) {
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to fetch app",
});
}
};
return {
openApp,

View File

@@ -1,15 +1,12 @@
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { executeEvent } from '../../utils/events';
import { useSetRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { saveFile } from '../../qortalRequests/get';
import { mimeToExtensionMap } from '../../utils/memeTypes';
import { MyContext } from '../../App';
import FileSaver from 'file-saver';
import { useSetAtom } from 'jotai';
export const saveFileInChunks = async (
blob: Blob,
@@ -45,7 +42,6 @@ export const saveFileInChunks = async (
// Map MIME type to file extension
const mimeTypeToExtension = (mimeType: string): string => {
return mimeToExtensionMap[mimeType] || existingExtension || ''; // Use existing extension if MIME type not found
};
@@ -76,21 +72,18 @@ export const saveFileInChunks = async (
offset += chunkSize;
isFirstChunk = false;
}
} catch (error) {
console.error('Error saving file in chunks:', error);
}
};
// Helper function to convert a Blob to a Base64 string
const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64data = reader.result?.toString().split(",")[1];
resolve(base64data || "");
const base64data = reader.result?.toString().split(',')[1];
resolve(base64data || '');
};
reader.onerror = reject;
reader.readAsDataURL(blob);
@@ -98,81 +91,81 @@ const blobToBase64 = (blob: Blob): Promise<string> => {
};
class Semaphore {
constructor(count) {
this.count = count
this.waiting = []
}
acquire() {
return new Promise(resolve => {
if (this.count > 0) {
this.count--
resolve()
} else {
this.waiting.push(resolve)
}
})
}
release() {
if (this.waiting.length > 0) {
const resolve = this.waiting.shift()
resolve()
} else {
this.count++
}
}
}
let semaphore = new Semaphore(1)
let reader = new FileReader()
const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
if (!reader) {
reader = new FileReader()
}
await semaphore.acquire()
reader.readAsDataURL(file)
reader.onload = () => {
const dataUrl = reader.result
if (typeof dataUrl === "string") {
const base64String = dataUrl.split(',')[1]
reader.onload = null
reader.onerror = null
resolve(base64String)
} else {
reader.onload = null
reader.onerror = null
reject(new Error('Invalid data URL'))
}
semaphore.release()
}
reader.onerror = (error) => {
reader.onload = null
reader.onerror = null
reject(error)
semaphore.release()
}
})
export function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("fileStorageDB", 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains("files")) {
db.createObjectStore("files", { keyPath: "id" });
}
};
request.onsuccess = function (event) {
resolve(event.target.result);
};
request.onerror = function () {
reject("Error opening IndexedDB");
};
constructor(count) {
this.count = count;
this.waiting = [];
}
acquire() {
return new Promise((resolve) => {
if (this.count > 0) {
this.count--;
resolve();
} else {
this.waiting.push(resolve);
}
});
}
release() {
if (this.waiting.length > 0) {
const resolve = this.waiting.shift();
resolve();
} else {
this.count++;
}
}
}
let semaphore = new Semaphore(1);
let reader = new FileReader();
const fileToBase64 = (file) =>
new Promise(async (resolve, reject) => {
if (!reader) {
reader = new FileReader();
}
await semaphore.acquire();
reader.readAsDataURL(file);
reader.onload = () => {
const dataUrl = reader.result;
if (typeof dataUrl === 'string') {
const base64String = dataUrl.split(',')[1];
reader.onload = null;
reader.onerror = null;
resolve(base64String);
} else {
reader.onload = null;
reader.onerror = null;
reject(new Error('Invalid data URL'));
}
semaphore.release();
};
reader.onerror = (error) => {
reader.onload = null;
reader.onerror = null;
reject(error);
semaphore.release();
};
});
export function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('fileStorageDB', 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains('files')) {
db.createObjectStore('files', { keyPath: 'id' });
}
};
request.onsuccess = function (event) {
resolve(event.target.result);
};
request.onerror = function () {
reject('Error opening IndexedDB');
};
});
}
export const listOfAllQortalRequests = [
'GET_USER_ACCOUNT',
@@ -209,7 +202,7 @@ export const listOfAllQortalRequests = [
'DECRYPT_QORTAL_GROUP_DATA',
'DECRYPT_DATA_WITH_SHARING_KEY',
'DELETE_HOSTED_DATA',
'GET_HOSTED_DATA',
'GET_HOSTED_DATA',
'PUBLISH_MULTIPLE_QDN_RESOURCES',
'PUBLISH_QDN_RESOURCE',
'ENCRYPT_DATA',
@@ -261,8 +254,8 @@ export const listOfAllQortalRequests = [
'CANCEL_SELL_NAME',
'BUY_NAME',
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'TRANSFER_ASSET'
]
'TRANSFER_ASSET',
];
export const UIQortalRequests = [
'GET_USER_ACCOUNT',
@@ -323,316 +316,327 @@ export const UIQortalRequests = [
'CANCEL_SELL_NAME',
'BUY_NAME',
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'TRANSFER_ASSET'
'TRANSFER_ASSET',
];
async function retrieveFileFromIndexedDB(fileId) {
const db = await openIndexedDB();
const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore('files');
return new Promise((resolve, reject) => {
const getRequest = objectStore.get(fileId);
async function retrieveFileFromIndexedDB(fileId) {
getRequest.onsuccess = function (event) {
if (getRequest.result) {
// File found, resolve it and delete from IndexedDB
const file = getRequest.result.data;
objectStore.delete(fileId);
resolve(file);
} else {
reject('File not found in IndexedDB');
}
};
getRequest.onerror = function () {
reject('Error retrieving file from IndexedDB');
};
});
}
async function deleteQortalFilesFromIndexedDB() {
try {
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
return new Promise((resolve, reject) => {
const getRequest = objectStore.get(fileId);
getRequest.onsuccess = function (event) {
if (getRequest.result) {
// File found, resolve it and delete from IndexedDB
const file = getRequest.result.data;
objectStore.delete(fileId);
resolve(file);
} else {
reject("File not found in IndexedDB");
const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore('files');
// Create a request to get all keys
const getAllKeysRequest = objectStore.getAllKeys();
getAllKeysRequest.onsuccess = function (event) {
const keys = event.target.result;
// Iterate through keys to find and delete those containing '_qortalfile'
for (let key of keys) {
if (key.includes('_qortalfile')) {
const deleteRequest = objectStore.delete(key);
deleteRequest.onsuccess = function () {
console.log(
`File with key '${key}' has been deleted from IndexedDB`
);
};
deleteRequest.onerror = function () {
console.error(
`Failed to delete file with key '${key}' from IndexedDB`
);
};
}
};
getRequest.onerror = function () {
reject("Error retrieving file from IndexedDB");
};
});
}
async function deleteQortalFilesFromIndexedDB() {
try {
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
// Create a request to get all keys
const getAllKeysRequest = objectStore.getAllKeys();
getAllKeysRequest.onsuccess = function (event) {
const keys = event.target.result;
// Iterate through keys to find and delete those containing '_qortalfile'
for (let key of keys) {
if (key.includes("_qortalfile")) {
const deleteRequest = objectStore.delete(key);
deleteRequest.onsuccess = function () {
console.log(`File with key '${key}' has been deleted from IndexedDB`);
};
deleteRequest.onerror = function () {
console.error(`Failed to delete file with key '${key}' from IndexedDB`);
};
}
}
};
getAllKeysRequest.onerror = function () {
console.error("Failed to retrieve keys from IndexedDB");
};
transaction.oncomplete = function () {
console.log("Transaction complete for deleting files from IndexedDB");
};
transaction.onerror = function () {
console.error("Error occurred during transaction for deleting files");
};
} catch (error) {
console.error("Error opening IndexedDB:", error);
}
}
};
getAllKeysRequest.onerror = function () {
console.error('Failed to retrieve keys from IndexedDB');
};
transaction.oncomplete = function () {
console.log('Transaction complete for deleting files from IndexedDB');
};
transaction.onerror = function () {
console.error('Error occurred during transaction for deleting files');
};
} catch (error) {
console.error('Error opening IndexedDB:', error);
}
}
export const showSaveFilePicker = async (
data,
{ openSnackGlobal, setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom }
) => {
try {
const { filename, mimeType, blob, fileHandleOptions } = data;
export const showSaveFilePicker = async (data, {openSnackGlobal,
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom}) => {
try {
const { filename, mimeType, blob, fileHandleOptions } = data;
setInfoSnackCustom({
type: "info",
message:
"Saving file...",
});
setOpenSnackGlobal(true);
FileSaver.saveAs(blob, filename)
setInfoSnackCustom({
type: "success",
message:
"Saving file success!",
setInfoSnackCustom({
type: 'info',
message: 'Saving file...',
});
setOpenSnackGlobal(true);
} catch (error) {
setInfoSnackCustom({
type: "error",
message:
error?.message ? `Error saving file: ${error?.message}` : 'Error saving file',
});
setOpenSnackGlobal(true);
console.error("Error saving file:", error);
FileSaver.saveAs(blob, filename);
setInfoSnackCustom({
type: 'success',
message: 'Saving file success!',
});
setOpenSnackGlobal(true);
} catch (error) {
setInfoSnackCustom({
type: 'error',
message: error?.message
? `Error saving file: ${error?.message}`
: 'Error saving file',
});
setOpenSnackGlobal(true);
console.error('Error saving file:', error);
}
};
declare var cordova: any;
async function storeFilesInIndexedDB(obj) {
// First delete any existing files in IndexedDB with '_qortalfile' in their ID
await deleteQortalFilesFromIndexedDB();
// Open the IndexedDB
const db = await openIndexedDB();
const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore('files');
// Handle the obj.file if it exists and is a File instance
if (obj.file) {
const fileId = Date.now() + 'objFile_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
obj.fileId = fileId;
delete obj.file;
}
if (obj.blob) {
const fileId = Date.now() + 'objFile_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.blob,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
let blobObj = {
type: obj.blob?.type,
};
obj.fileId = fileId;
delete obj.blob;
obj.blob = blobObj;
}
// Iterate through resources to find files and save them to IndexedDB
for (let resource of obj?.resources || []) {
if (resource.file) {
const fileId = resource.identifier + Date.now() + '_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: resource.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
resource.fileId = fileId;
delete resource.file;
}
}
// Set transaction completion handlers
transaction.oncomplete = function () {
console.log('Files saved successfully to IndexedDB');
};
declare var cordova: any;
transaction.onerror = function () {
console.error('Error saving files to IndexedDB');
};
return obj; // Updated object with references to stored files
}
async function storeFilesInIndexedDB(obj) {
// First delete any existing files in IndexedDB with '_qortalfile' in their ID
await deleteQortalFilesFromIndexedDB();
// Open the IndexedDB
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
// Handle the obj.file if it exists and is a File instance
if (obj.file) {
const fileId = Date.now() + "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
obj.fileId = fileId;
delete obj.file;
}
if (obj.blob) {
const fileId = Date.now() + "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.blob,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
let blobObj = {
type: obj.blob?.type
}
obj.fileId = fileId;
delete obj.blob;
obj.blob = blobObj
}
// Iterate through resources to find files and save them to IndexedDB
for (let resource of (obj?.resources || [])) {
if (resource.file) {
const fileId = resource.identifier + Date.now() + "_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: resource.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
resource.fileId = fileId;
delete resource.file;
}
}
// Set transaction completion handlers
transaction.oncomplete = function () {
console.log("Files saved successfully to IndexedDB");
};
transaction.onerror = function () {
console.error("Error saving files to IndexedDB");
};
return obj; // Updated object with references to stored files
}
export const useQortalMessageListener = (frameWindow, iframeRef, tabId, isDevMode, appName, appService, skipAuth) => {
const [path, setPath] = useState('')
export const useQortalMessageListener = (
frameWindow,
iframeRef,
tabId,
isDevMode,
appName,
appService,
skipAuth
) => {
const [path, setPath] = useState('');
const [history, setHistory] = useState({
customQDNHistoryPaths: [],
currentIndex: -1,
isDOMContentLoaded: false
})
const setHasSettingsChangedAtom = useSetRecoilState(navigationControllerAtom);
const { openSnackGlobal,
currentIndex: -1,
isDOMContentLoaded: false,
});
const setHasSettingsChangedAtom = useSetAtom(navigationControllerAtom);
const {
openSnackGlobal,
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom } = useContext(MyContext);
setInfoSnackCustom,
} = useContext(MyContext);
useEffect(()=> {
if(tabId && !isNaN(history?.currentIndex)){
setHasSettingsChangedAtom((prev)=> {
useEffect(() => {
if (tabId && !isNaN(history?.currentIndex)) {
setHasSettingsChangedAtom((prev) => {
return {
...prev,
[tabId]: {
hasBack: history?.currentIndex > 0,
}
}
})
},
};
});
}
}, [history?.currentIndex, tabId])
}, [history?.currentIndex, tabId]);
const changeCurrentIndex = useCallback((value)=> {
setHistory((prev)=> {
const changeCurrentIndex = useCallback((value) => {
setHistory((prev) => {
return {
...prev,
currentIndex: value
}
})
}, [])
currentIndex: value,
};
});
}, []);
const resetHistory = useCallback(()=> {
const resetHistory = useCallback(() => {
setHistory({
customQDNHistoryPaths: [],
currentIndex: -1,
isManualNavigation: true,
isDOMContentLoaded: false
})
}, [])
currentIndex: -1,
isManualNavigation: true,
isDOMContentLoaded: false,
});
}, []);
useEffect(() => {
const listener = async (event) => {
if (event?.data?.requestedHandler !== 'UI') return;
const sendMessageToRuntime = (message, eventPort) => {
window.sendMessage(message.action, message.payload, 300000, message.isExtension, {
name: appName, service: appService
}, skipAuth)
.then((response) => {
if (response.error) {
eventPort.postMessage({
result: null,
error: {
error: response?.error,
message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred'
},
});
} else {
eventPort.postMessage({
result: response,
error: null,
});
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
window
.sendMessage(
message.action,
message.payload,
300000,
message.isExtension,
{
name: appName,
service: appService,
},
skipAuth
)
.then((response) => {
if (response.error) {
eventPort.postMessage({
result: null,
error: {
error: response?.error,
message:
typeof response?.error === 'string'
? response?.error
: typeof response?.message === 'string'
? response?.message
: 'An error has occurred',
},
});
} else {
eventPort.postMessage({
result: response,
error: null,
});
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
};
// Check if action is included in the predefined list of UI requests
if (UIQortalRequests.includes(event.data.action)) {
sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true },
{
action: event.data.action,
type: 'qortalRequest',
payload: event.data,
isExtension: true,
},
event.ports[0]
);
} else if(event?.data?.action === 'SAVE_FILE'
){
} else if (event?.data?.action === 'SAVE_FILE') {
try {
const res = await saveFile( event.data, null, true, {
openSnackGlobal,
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom
const res = await saveFile(event.data, null, true, {
openSnackGlobal,
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom,
});
} catch (error) {
}
} catch (error) {}
} else if (
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
event?.data?.action === 'ENCRYPT_DATA' ||
event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' ||
event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
) {
const data = event.data;
if (data) {
sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: data, isExtension: true },
{
action: event.data.action,
type: 'qortalRequest',
payload: data,
isExtension: true,
},
event.ports[0]
);
} else {
@@ -641,52 +645,73 @@ isDOMContentLoaded: false
error: 'Failed to prepare data for publishing',
});
}
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
setPath(pathUrl)
if(appName?.toLowerCase() === 'q-mail'){
window.sendMessage("addEnteredQmailTimestamp").catch((error) => {
} else if (
event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'
) {
const pathUrl =
event?.data?.path != null
? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path
: null;
setPath(pathUrl);
if (appName?.toLowerCase() === 'q-mail') {
window.sendMessage('addEnteredQmailTimestamp').catch((error) => {
// error
});
} else if(appName?.toLowerCase() === 'q-wallets'){
executeEvent('setLastEnteredTimestampPaymentEvent', {})
} else if (appName?.toLowerCase() === 'q-wallets') {
executeEvent('setLastEnteredTimestampPaymentEvent', {});
}
} else if(event?.data?.action === 'NAVIGATION_HISTORY'){
if(event?.data?.payload?.isDOMContentLoaded){
setHistory((prev)=> {
const copyPrev = {...prev}
if((copyPrev?.customQDNHistoryPaths || []).at(-1) === (event?.data?.payload?.customQDNHistoryPaths || []).at(-1)) {
} else if (event?.data?.action === 'NAVIGATION_HISTORY') {
if (event?.data?.payload?.isDOMContentLoaded) {
setHistory((prev) => {
const copyPrev = { ...prev };
if (
(copyPrev?.customQDNHistoryPaths || []).at(-1) ===
(event?.data?.payload?.customQDNHistoryPaths || []).at(-1)
) {
return {
...prev,
currentIndex: prev.customQDNHistoryPaths.length - 1 === -1 ? 0 : prev.customQDNHistoryPaths.length - 1
}
currentIndex:
prev.customQDNHistoryPaths.length - 1 === -1
? 0
: prev.customQDNHistoryPaths.length - 1,
};
}
const copyHistory = {...prev}
const paths = [...(copyHistory?.customQDNHistoryPaths.slice(0, copyHistory.currentIndex + 1) || []), ...(event?.data?.payload?.customQDNHistoryPaths || [])]
const copyHistory = { ...prev };
const paths = [
...(copyHistory?.customQDNHistoryPaths.slice(
0,
copyHistory.currentIndex + 1
) || []),
...(event?.data?.payload?.customQDNHistoryPaths || []),
];
return {
...prev,
customQDNHistoryPaths: paths,
currentIndex: paths.length - 1
}
})
currentIndex: paths.length - 1,
};
});
} else {
setHistory(event?.data?.payload)
setHistory(event?.data?.payload);
}
} else if(event?.data?.action === 'SET_TAB' && !isDevMode){
executeEvent("addTab", {
data: event?.data?.payload
})
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
} else if (event?.data?.action === 'SET_TAB' && !isDevMode) {
executeEvent('addTab', {
data: event?.data?.payload,
});
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
iframeRef.current.contentWindow.postMessage(
{ action: 'SET_TAB_SUCCESS', requestedHandler: 'UI',payload: {
name: event?.data?.payload?.name
} }, targetOrigin
{
action: 'SET_TAB_SUCCESS',
requestedHandler: 'UI',
payload: {
name: event?.data?.payload?.name,
},
},
targetOrigin
);
}
}
};
// Add the listener for messages coming from the frameWindow
@@ -696,12 +721,7 @@ isDOMContentLoaded: false
return () => {
frameWindow.removeEventListener('message', listener);
};
}, [isDevMode, appName, appService]); // Empty dependency array to run once when the component mounts
return {path, history, resetHistory, changeCurrentIndex}
return { path, history, resetHistory, changeCurrentIndex };
};

View File

@@ -0,0 +1,263 @@
import {
Box,
Checkbox,
FormControlLabel,
Typography,
useTheme,
} from '@mui/material';
import { Spacer } from '../../common/Spacer';
import { Return } from '../../assets/Icons/Return';
import { CustomButton, CustomLabel, TextP } from '../../styles/App-styles';
import { PasswordField } from '../PasswordField/PasswordField';
import { ErrorText } from '../ErrorText/ErrorText';
import Logo1Dark from '../../assets/svgs/Logo1Dark.svg';
import { useTranslation } from 'react-i18next';
import { saveFileToDisk } from '../../utils/generateWallet/generateWallet';
import { useState } from 'react';
import { decryptStoredWallet } from '../../utils/decryptWallet';
import PhraseWallet from '../../utils/generateWallet/phrase-wallet';
import { crypto, walletVersion } from '../../constants/decryptWallet';
export const DownloadWallet = ({
returnToMain,
setIsLoading,
showInfo,
rawWallet,
setWalletToBeDownloaded,
walletToBeDownloaded,
}) => {
const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] =
useState<string>('');
const [newPassword, setNewPassword] = useState<string>('');
const [keepCurrentPassword, setKeepCurrentPassword] = useState<boolean>(true);
const theme = useTheme();
const [walletToBeDownloadedError, setWalletToBeDownloadedError] =
useState<string>('');
const { t } = useTranslation(['auth']);
const saveFileToDiskFunc = async () => {
try {
await saveFileToDisk(
walletToBeDownloaded.wallet,
walletToBeDownloaded.qortAddress
);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
}
};
const saveWalletFunc = async (password: string, newPassword) => {
let wallet = structuredClone(rawWallet);
const res = await decryptStoredWallet(password, wallet);
const wallet2 = new PhraseWallet(res, wallet?.version || walletVersion);
const passwordToUse = newPassword || password;
wallet = await wallet2.generateSaveWalletData(
passwordToUse,
crypto.kdfThreads,
() => {}
);
setWalletToBeDownloaded({
wallet,
qortAddress: rawWallet.address0,
});
return {
wallet,
qortAddress: rawWallet.address0,
};
};
const confirmPasswordToDownload = async () => {
try {
setWalletToBeDownloadedError('');
if (!keepCurrentPassword && !newPassword) {
setWalletToBeDownloadedError(
t('auth:wallet.error.missing_new_password', {
postProcess: 'capitalize',
})
);
return;
}
if (!walletToBeDownloadedPassword) {
setWalletToBeDownloadedError(
t('auth:wallet.error.missing_password', { postProcess: 'capitalize' })
);
return;
}
setIsLoading(true);
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 250);
});
const newPasswordForWallet = !keepCurrentPassword ? newPassword : null;
const res = await saveWalletFunc(
walletToBeDownloadedPassword,
newPasswordForWallet
);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
} finally {
setIsLoading(false);
}
};
return (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
width: 'auto',
}}
onClick={returnToMain}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="35px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<TextP
sx={{
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 600,
}}
>
{t('auth:download_account', { postProcess: 'capitalize' })}
</TextP>
</Box>
<Spacer height="35px" />
{!walletToBeDownloaded && (
<>
<CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.password_confirmation', {
postProcess: 'capitalize',
})}
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
onChange={(e) => setWalletToBeDownloadedPassword(e.target.value)}
/>
<Spacer height="20px" />
<FormControlLabel
sx={{
margin: 0,
}}
control={
<Checkbox
onChange={(e) => setKeepCurrentPassword(e.target.checked)}
checked={keepCurrentPassword}
edge="start"
tabIndex={-1}
disableRipple
sx={{
'&.Mui-checked': {
color: theme.palette.text.secondary,
},
'& .MuiSvgIcon-root': {
color: theme.palette.text.secondary,
},
}}
/>
}
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: '14px' }}>
{t('auth:wallet.keep_password', {
postProcess: 'capitalize',
})}
</Typography>
</Box>
}
/>
<Spacer height="20px" />
{!keepCurrentPassword && (
<>
<CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.new_password', {
postProcess: 'capitalize',
})}
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<Spacer height="20px" />
</>
)}
<CustomButton onClick={confirmPasswordToDownload}>
{t('auth:password_confirmation', {
postProcess: 'capitalize',
})}
</CustomButton>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
)}
{walletToBeDownloaded && (
<>
<CustomButton
onClick={async () => {
await saveFileToDiskFunc();
await showInfo({
message: t('auth:keep_secure', {
postProcess: 'capitalize',
}),
});
}}
>
{t('auth:download_account', {
postProcess: 'capitalize',
})}
</CustomButton>
</>
)}
</>
);
};

Some files were not shown because too many files have changed in this diff Show More