Add theme

This commit is contained in:
Nicola Benaglia 2025-04-19 07:56:09 +02:00
parent 3a302cf5b2
commit 18ec6126b7
3 changed files with 748 additions and 673 deletions

View File

@ -8,64 +8,78 @@ import {
DialogTitle,
TextField,
Typography,
} from "@mui/material";
import React, { useContext, useEffect, useState } from "react";
import { getBaseApiReact, MyContext } from "../../App";
import { Spacer } from "../../common/Spacer";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { validateAddress } from "../../utils/validateAddress";
import { getNameInfo, requestQueueMemberNames } from "./Group";
import { useModal } from "../../common/useModal";
import { useRecoilState } from "recoil";
import { isOpenBlockedModalAtom } from "../../atoms/global";
useTheme,
} from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import { getBaseApiReact, MyContext } from '../../App';
import { Spacer } from '../../common/Spacer';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from '../../utils/events';
import { validateAddress } from '../../utils/validateAddress';
import { getNameInfo, requestQueueMemberNames } from './Group';
import { useModal } from '../../common/useModal';
import { useRecoilState } from 'recoil';
import { isOpenBlockedModalAtom } from '../../atoms/global';
import InfoIcon from '@mui/icons-material/Info';
export const BlockedUsersModal = () => {
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(isOpenBlockedModalAtom)
const theme = useTheme();
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(
isOpenBlockedModalAtom
);
const [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState("");
const [addressesWithNames, setAddressesWithNames] = useState({})
const [value, setValue] = useState('');
const [addressesWithNames, setAddressesWithNames] = useState({});
const { isShow, onCancel, onOk, show, message } = useModal();
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
useContext(MyContext);
const {
getAllBlockedUsers,
removeBlockFromList,
addToBlockList,
setOpenSnackGlobal,
setInfoSnackCustom,
} = useContext(MyContext);
const [blockedUsers, setBlockedUsers] = useState({
addresses: {},
names: {},
});
const fetchBlockedUsers = () => {
setBlockedUsers(getAllBlockedUsers());
};
useEffect(() => {
if(!isOpenBlockedModal) return
if (!isOpenBlockedModal) return;
fetchBlockedUsers();
}, [isOpenBlockedModal]);
const getNames = async () => {
const getNames = async () => {
// const validApi = await findUsableApi();
const addresses = Object.keys(blockedUsers?.addresses)
const addressNames = {}
const addresses = Object.keys(blockedUsers?.addresses);
const addressNames = {};
const getMemNames = addresses.map(async (address) => {
const name = await requestQueueMemberNames.enqueue(() => {
return getNameInfo(address);
});
if (name) {
addressNames[address] = name
}
const name = await requestQueueMemberNames.enqueue(() => {
return getNameInfo(address);
});
if (name) {
addressNames[address] = name;
}
return true;
});
await Promise.all(getMemNames);
setAddressesWithNames(addressNames)
setAddressesWithNames(addressNames);
};
const blockUser = async (e, user?: string) => {
try {
const valUser = user || value
const valUser = user || value;
if (!valUser) return;
const isAddress = validateAddress(valUser);
let userName = null;
@ -80,62 +94,66 @@ export const BlockedUsersModal = () => {
if (!isAddress) {
const response = await fetch(`${getBaseApiReact()}/names/${valUser}`);
const data = await response.json();
if (!data?.owner) throw new Error("Name does not exist");
if (!data?.owner) throw new Error('Name does not exist');
if (data?.owner) {
userAddress = data.owner;
userName = valUser;
}
}
if(!userName){
if (!userName) {
await addToBlockList(userAddress, null);
fetchBlockedUsers();
setHasChanged(true);
executeEvent('updateChatMessagesWithBlocks', true)
setValue('')
return
executeEvent('updateChatMessagesWithBlocks', true);
setValue('');
return;
}
const responseModal = await show({
userName,
userAddress,
});
if (responseModal === "both") {
if (responseModal === 'both') {
await addToBlockList(userAddress, userName);
} else if (responseModal === "address") {
} else if (responseModal === 'address') {
await addToBlockList(userAddress, null);
} else if (responseModal === "name") {
} else if (responseModal === 'name') {
await addToBlockList(null, userName);
}
fetchBlockedUsers();
setHasChanged(true);
setValue('')
if(user){
setIsOpenBlockedModal(false)
setValue('');
if (user) {
setIsOpenBlockedModal(false);
}
if(responseModal === 'both' || responseModal === 'address'){
executeEvent('updateChatMessagesWithBlocks', true)
if (responseModal === 'both' || responseModal === 'address') {
executeEvent('updateChatMessagesWithBlocks', true);
}
} catch (error) {
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to block user",
});
setInfoSnackCustom({
type: 'error',
message: error?.message || 'Unable to block user',
});
}
};
const blockUserFromOutsideModalFunc = (e) => {
const user = e.detail?.user;
setIsOpenBlockedModal(true)
blockUser(null, user)
const user = e.detail?.user;
setIsOpenBlockedModal(true);
blockUser(null, user);
};
useEffect(() => {
subscribeToEvent('blockUserFromOutside', blockUserFromOutsideModalFunc);
return () => {
unsubscribeFromEvent(
'blockUserFromOutside',
blockUserFromOutsideModalFunc
);
};
useEffect(() => {
subscribeToEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
return () => {
unsubscribeFromEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
};
}, []);
}, []);
return (
<Dialog
open={isOpenBlockedModal}
@ -145,14 +163,14 @@ export const BlockedUsersModal = () => {
<DialogTitle>Blocked Users</DialogTitle>
<DialogContent
sx={{
padding: "20px",
padding: '20px',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
alignItems: 'center',
display: 'flex',
gap: '10px',
}}
>
<TextField
@ -180,16 +198,18 @@ export const BlockedUsersModal = () => {
Blocked addresses- blocks processing of txs
</DialogContentText>
<Spacer height="10px" />
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
<Button variant="contained" size="small" onClick={getNames}>
Fetch names
</Button>
<Spacer height="10px" />
</>
)}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "10px",
display: 'flex',
flexDirection: 'column',
gap: '10px',
}}
>
{Object.entries(blockedUsers?.addresses || {})?.map(
@ -197,11 +217,11 @@ export const BlockedUsersModal = () => {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
width: "100%",
justifyContent: "space-between",
alignItems: 'center',
display: 'flex',
gap: '10px',
justifyContent: 'space-between',
width: '100%',
}}
>
<Typography>{addressesWithNames[key] || key}</Typography>
@ -215,7 +235,7 @@ export const BlockedUsersModal = () => {
try {
await removeBlockFromList(key, undefined);
setHasChanged(true);
setValue("");
setValue('');
fetchBlockedUsers();
} catch (error) {
console.error(error);
@ -241,20 +261,20 @@ export const BlockedUsersModal = () => {
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "10px",
display: 'flex',
flexDirection: 'column',
gap: '10px',
}}
>
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
width: "100%",
justifyContent: "space-between",
alignItems: 'center',
display: 'flex',
gap: '10px',
justifyContent: 'space-between',
width: '100%',
}}
>
<Typography>{key}</Typography>
@ -284,20 +304,20 @@ export const BlockedUsersModal = () => {
<DialogActions>
<Button
sx={{
backgroundColor: "var(--green)",
color: "black",
fontWeight: "bold",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontWeight: 'bold',
opacity: 0.7,
"&:hover": {
backgroundColor: "var(--green)",
color: "black",
'&:hover': {
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
opacity: 1,
},
}}
variant="contained"
onClick={() => {
if (hasChanged) {
executeEvent("updateChatMessagesWithBlocks", true);
executeEvent('updateChatMessagesWithBlocks', true);
}
setIsOpenBlockedModal(false);
}}
@ -312,28 +332,37 @@ export const BlockedUsersModal = () => {
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Decide what to block"}
{'Decide what to block'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Blocking {message?.userName || message?.userAddress}
</DialogContentText>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: '10px',
marginTop: '20px'
}}>
<InfoIcon sx={{
color: 'fff'
}}/> <Typography>Choose "block txs" or "all" to block chat messages </Typography>
<Box
sx={{
alignItems: 'center',
display: 'flex',
gap: '10px',
marginTop: '20px',
}}
>
<InfoIcon
sx={{
color: theme.palette.text.primary,
}}
/>{' '}
<Typography>
Choose "block txs" or "all" to block chat messages{' '}
</Typography>
</Box>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => {
onOk("address");
onOk('address');
}}
>
Block txs
@ -341,7 +370,7 @@ export const BlockedUsersModal = () => {
<Button
variant="contained"
onClick={() => {
onOk("name");
onOk('name');
}}
>
Block QDN data
@ -349,7 +378,7 @@ export const BlockedUsersModal = () => {
<Button
variant="contained"
onClick={() => {
onOk("both");
onOk('both');
}}
>
Block All

View File

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from "react";
import { DrawerUserLookup } from "../Drawer/DrawerUserLookup";
import { useCallback, useEffect, useState } from 'react';
import { DrawerUserLookup } from '../Drawer/DrawerUserLookup';
import {
Avatar,
Box,
@ -16,16 +16,21 @@ import {
Typography,
Table,
CircularProgress,
} from "@mui/material";
import { getAddressInfo, getNameOrAddress } from "../../background";
import { getBaseApiReact } from "../../App";
import { getNameInfo } from "../Group/Group";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import { Spacer } from "../../common/Spacer";
import { formatTimestamp } from "../../utils/time";
useTheme,
} from '@mui/material';
import { getAddressInfo, getNameOrAddress } from '../../background';
import { getBaseApiReact } from '../../App';
import { getNameInfo } from '../Group/Group';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import { Spacer } from '../../common/Spacer';
import { formatTimestamp } from '../../utils/time';
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
import SearchIcon from '@mui/icons-material/Search';
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from '../../utils/events';
function formatAddress(str) {
if (str.length <= 12) return str;
@ -37,469 +42,511 @@ function formatAddress(str) {
}
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
const [nameOrAddress, setNameOrAddress] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const theme = useTheme();
const [nameOrAddress, setNameOrAddress] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [addressInfo, setAddressInfo] = useState(null);
const [isLoadingUser, setIsLoadingUser] = useState(false);
const [isLoadingPayments, setIsLoadingPayments] = useState(false);
const [payments, setPayments] = useState([]);
const lookupFunc = useCallback(async (messageAddressOrName) => {
try {
setErrorMessage('')
setIsLoadingUser(true)
setPayments([])
setAddressInfo(null)
const inputAddressOrName = messageAddressOrName || nameOrAddress
if (!inputAddressOrName?.trim())
throw new Error("Please insert a name or address");
const owner = await getNameOrAddress(inputAddressOrName);
if (!owner) throw new Error("Name does not exist");
const addressInfoRes = await getAddressInfo(owner);
if (!addressInfoRes?.publicKey) {
throw new Error("Address does not exist on blockchain");
const lookupFunc = useCallback(
async (messageAddressOrName) => {
try {
setErrorMessage('');
setIsLoadingUser(true);
setPayments([]);
setAddressInfo(null);
const inputAddressOrName = messageAddressOrName || nameOrAddress;
if (!inputAddressOrName?.trim())
throw new Error('Please insert a name or address');
const owner = await getNameOrAddress(inputAddressOrName);
if (!owner) throw new Error('Name does not exist');
const addressInfoRes = await getAddressInfo(owner);
if (!addressInfoRes?.publicKey) {
throw new Error('Address does not exist on blockchain');
}
const name = await getNameInfo(owner);
const balanceRes = await fetch(
`${getBaseApiReact()}/addresses/balance/${owner}`
);
const balanceData = await balanceRes.json();
setAddressInfo({
...addressInfoRes,
balance: balanceData,
name,
});
setIsLoadingUser(false);
setIsLoadingPayments(true);
const getPayments = await fetch(
`${getBaseApiReact()}/transactions/search?txType=PAYMENT&address=${owner}&confirmationStatus=CONFIRMED&limit=20&reverse=true`
);
const paymentsData = await getPayments.json();
setPayments(paymentsData);
} catch (error) {
setErrorMessage(error?.message);
console.error(error);
} finally {
setIsLoadingUser(false);
setIsLoadingPayments(false);
}
const name = await getNameInfo(owner);
const balanceRes = await fetch(
`${getBaseApiReact()}/addresses/balance/${owner}`
);
const balanceData = await balanceRes.json();
setAddressInfo({
...addressInfoRes,
balance: balanceData,
name,
});
setIsLoadingUser(false)
setIsLoadingPayments(true)
},
[nameOrAddress]
);
const getPayments = await fetch(
`${getBaseApiReact()}/transactions/search?txType=PAYMENT&address=${owner}&confirmationStatus=CONFIRMED&limit=20&reverse=true`
);
const paymentsData = await getPayments.json();
setPayments(paymentsData);
} catch (error) {
setErrorMessage(error?.message)
console.error(error);
} finally {
setIsLoadingUser(false)
setIsLoadingPayments(false)
}
}, [nameOrAddress]);
const openUserLookupDrawerFunc = useCallback((e) => {
setIsOpenDrawerLookup(true)
const openUserLookupDrawerFunc = useCallback(
(e) => {
setIsOpenDrawerLookup(true);
const message = e.detail?.addressOrName;
if(message){
lookupFunc(message)
if (message) {
lookupFunc(message);
}
}, [lookupFunc, setIsOpenDrawerLookup]);
useEffect(() => {
subscribeToEvent("openUserLookupDrawer", openUserLookupDrawerFunc);
return () => {
unsubscribeFromEvent("openUserLookupDrawer", openUserLookupDrawerFunc);
};
}, [openUserLookupDrawerFunc]);
},
[lookupFunc, setIsOpenDrawerLookup]
);
const onClose = ()=> {
setIsOpenDrawerLookup(false)
setNameOrAddress('')
setErrorMessage('')
setPayments([])
setIsLoadingUser(false)
setIsLoadingPayments(false)
setAddressInfo(null)
}
useEffect(() => {
subscribeToEvent('openUserLookupDrawer', openUserLookupDrawerFunc);
return () => {
unsubscribeFromEvent('openUserLookupDrawer', openUserLookupDrawerFunc);
};
}, [openUserLookupDrawerFunc]);
const onClose = () => {
setIsOpenDrawerLookup(false);
setNameOrAddress('');
setErrorMessage('');
setPayments([]);
setIsLoadingUser(false);
setIsLoadingPayments(false);
setAddressInfo(null);
};
return (
<DrawerUserLookup open={isOpenDrawerLookup} setOpen={setIsOpenDrawerLookup}>
<Box
sx={{
display: "flex",
flexDirection: "column",
padding: "15px",
height: "100vh",
overflow: "hidden",
display: 'flex',
flexDirection: 'column',
height: '100vh',
overflow: 'hidden',
padding: '15px',
}}
>
<Box
<Box
sx={{
display: "flex",
gap: "5px",
alignItems: "center",
alignItems: 'center',
display: 'flex',
flexShrink: 0,
gap: '5px',
}}
>
<TextField
autoFocus
autoFocus
value={nameOrAddress}
onChange={(e) => setNameOrAddress(e.target.value)}
size="small"
placeholder="Address or Name"
autoComplete="off"
onKeyDown={(e) => {
if (e.key === "Enter" && nameOrAddress) {
if (e.key === 'Enter' && nameOrAddress) {
lookupFunc();
}
}}
/>
<ButtonBase onClick={()=> {
lookupFunc();
}} >
<SearchIcon sx={{
color: 'white',
marginRight: '20px'
}} />
<ButtonBase
onClick={() => {
lookupFunc();
}}
>
<SearchIcon
sx={{
color: theme.palette.text.primary,
marginRight: '20px',
}}
/>
</ButtonBase>
<ButtonBase sx={{
marginLeft: 'auto',
}} onClick={()=> {
onClose()
}}>
<CloseFullscreenIcon sx={{
color: 'white'
}} />
<ButtonBase
sx={{
marginLeft: 'auto',
}}
onClick={() => {
onClose();
}}
>
<CloseFullscreenIcon
sx={{
color: theme.palette.text.primary,
}}
/>
</ButtonBase>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: "auto",
overflow: 'auto',
}}
>
{!isLoadingUser && errorMessage && (
<Box sx={{
{!isLoadingUser && errorMessage && (
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'center',
marginTop: '40px'
}}>
<Typography>{errorMessage}</Typography>
marginTop: '40px',
width: '100%',
}}
>
<Typography>{errorMessage}</Typography>
</Box>
)}
{isLoadingUser && (
<Box sx={{
)}
{isLoadingUser && (
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'center',
marginTop: '40px'
}}>
<CircularProgress sx={{
color: 'white'
}} />
marginTop: '40px',
width: '100%',
}}
>
<CircularProgress
sx={{
color: theme.palette.text.primary,
}}
/>
</Box>
)}
{!isLoadingUser && addressInfo && (
)}
{!isLoadingUser && addressInfo && (
<>
<Spacer height="30px" />
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
flexDirection: "row",
width: "100%",
justifyContent: "center",
}}
>
<Card
sx={{
padding: "15px",
minWidth: "320px",
alignItems: "center",
minHeight: "200px",
background: "var(--bg-primary)",
display: "flex",
flexDirection: "column",
}}
>
<Typography
sx={{
textAlign: "center",
}}
>
{addressInfo?.name ?? "Name not registered"}
</Typography>
<Spacer height="20px" />
<Divider>
{addressInfo?.name ? (
<Avatar
sx={{
height: "50px",
width: "50px",
"& img": {
objectFit: "fill",
},
}}
alt={addressInfo?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
addressInfo?.name
}/qortal_avatar?async=true`}
>
<AccountCircleIcon
sx={{
fontSize: "50px",
}}
/>
</Avatar>
) : (
<AccountCircleIcon
sx={{
fontSize: "50px",
}}
/>
)}
</Divider>
<Spacer height="20px" />
<Typography
sx={{
textAlign: "center",
}}
>
Level {addressInfo?.level}
</Typography>
</Card>
<Card
sx={{
padding: "15px",
minWidth: "320px",
minHeight: "200px",
gap: "20px",
display: "flex",
flexDirection: "column",
background: "var(--bg-primary)",
}}
>
<Spacer height="30px" />
<Box
sx={{
display: "flex",
gap: "20px",
justifyContent: "space-between",
width: "100%",
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
gap: '20px',
justifyContent: 'center',
width: '100%',
}}
>
<Card
sx={{
alignItems: 'center',
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
minHeight: '200px',
minWidth: '320px',
padding: '15px',
}}
>
<Typography
sx={{
textAlign: 'center',
}}
>
{addressInfo?.name ?? 'Name not registered'}
</Typography>
<Spacer height="20px" />
<Divider>
{addressInfo?.name ? (
<Avatar
sx={{
height: '50px',
width: '50px',
'& img': {
objectFit: 'fill',
},
}}
alt={addressInfo?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
addressInfo?.name
}/qortal_avatar?async=true`}
>
<AccountCircleIcon
sx={{
fontSize: '50px',
}}
/>
</Avatar>
) : (
<AccountCircleIcon
sx={{
fontSize: '50px',
}}
/>
)}
</Divider>
<Spacer height="20px" />
<Typography
sx={{
textAlign: 'center',
}}
>
Level {addressInfo?.level}
</Typography>
</Card>
<Card
sx={{
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
gap: '20px',
minHeight: '200px',
minWidth: '320px',
padding: '15px',
}}
>
<Box
sx={{
display: 'flex',
gap: '20px',
justifyContent: 'space-between',
width: '100%',
}}
>
<Box
sx={{
display: 'flex',
flexShrink: 0,
}}
>
<Typography>Address</Typography>
</Box>
<Tooltip
title={
<span
style={{
color: theme.palette.text.primary,
fontSize: '14px',
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(addressInfo?.address);
}}
>
<Typography
sx={{
textAlign: 'end',
}}
>
{addressInfo?.address}
</Typography>
</ButtonBase>
</Tooltip>
</Box>
<Box
sx={{
display: 'flex',
gap: '20px',
justifyContent: 'space-between',
width: '100%',
}}
>
<Typography>Balance</Typography>
<Typography>{addressInfo?.balance}</Typography>
</Box>
<Spacer height="20px" />
<Button
variant="contained"
onClick={() => {
executeEvent('openPaymentInternal', {
address: addressInfo?.address,
name: addressInfo?.name,
});
}}
>
Send QORT
</Button>
</Card>
</Box>
</>
)}
<Spacer height="40px" />
{isLoadingPayments && (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<CircularProgress
sx={{
color: theme.palette.text.primary,
}}
/>
</Box>
)}
{!isLoadingPayments && addressInfo && (
<Card
sx={{
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
overflow: 'auto',
padding: '15px',
}}
>
<Typography>20 most recent payments</Typography>
<Spacer height="20px" />
{!isLoadingPayments && payments?.length === 0 && (
<Box
sx={{
display: "flex",
flexShrink: 0,
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<Typography>Address</Typography>
<Typography>No payments</Typography>
</Box>
<Tooltip
title={
<span
style={{
color: "white",
fontSize: "14px",
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: "24" }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
},
},
arrow: {
sx: {
color: "#444444",
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(addressInfo?.address);
}}
>
<Typography
sx={{
textAlign: "end",
}}
>
{addressInfo?.address}
</Typography>
</ButtonBase>
</Tooltip>
</Box>
<Box
sx={{
display: "flex",
gap: "20px",
justifyContent: "space-between",
width: "100%",
}}
>
<Typography>Balance</Typography>
<Typography>{addressInfo?.balance}</Typography>
</Box>
<Spacer height="20px" />
<Button variant="contained" onClick={()=> {
executeEvent('openPaymentInternal', {
address: addressInfo?.address,
name: addressInfo?.name,
});
}}>Send QORT</Button>
</Card>
</Box>
)}
</>
)}
<Spacer height="40px" />
{isLoadingPayments && (
<Box sx={{
display: 'flex',
width: '100%',
justifyContent: 'center'
}}>
<CircularProgress sx={{
color: 'white'
}} />
</Box>
)}
{!isLoadingPayments && addressInfo && (
<Card
sx={{
padding: "15px",
overflow: "auto",
display: "flex",
flexDirection: "column",
background: "var(--bg-primary)",
}}
>
<Typography>20 most recent payments</Typography>
<Spacer height="20px" />
{!isLoadingPayments && payments?.length === 0 && (
<Box sx={{
display: 'flex',
width: '100%',
justifyContent: 'center'
}}>
<Typography>No payments</Typography>
</Box>
)}
<Table>
<TableHead>
<TableRow>
<TableCell>Sender</TableCell>
<TableCell>Reciver</TableCell>
<TableCell>Amount</TableCell>
<TableCell>Time</TableCell>
</TableRow>
</TableHead>
<TableBody>
{payments.map((payment, index) => (
<TableRow key={payment?.signature}>
<TableCell>
<Tooltip
title={
<span
style={{
color: "white",
fontSize: "14px",
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: "24" }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
},
},
arrow: {
sx: {
color: "#444444",
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(
payment?.creatorAddress
);
}}
>
{formatAddress(payment?.creatorAddress)}
</ButtonBase>
</Tooltip>
</TableCell>
<TableCell>
<Tooltip
title={
<span
style={{
color: "white",
fontSize: "14px",
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: "24" }}
slotProps={{
tooltip: {
sx: {
color: "#ffffff",
backgroundColor: "#444444",
},
},
arrow: {
sx: {
color: "#444444",
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(payment?.recipient);
}}
>
{formatAddress(payment?.recipient)}
</ButtonBase>
</Tooltip>
</TableCell>
<TableCell>
{payment?.amount}
</TableCell>
<TableCell>{formatTimestamp(payment?.timestamp)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
<Table>
<TableHead>
<TableRow>
<TableCell>Sender</TableCell>
<TableCell>Reciver</TableCell>
<TableCell>Amount</TableCell>
<TableCell>Time</TableCell>
</TableRow>
</TableHead>
<TableBody>
{payments.map((payment, index) => (
<TableRow key={payment?.signature}>
<TableCell>
<Tooltip
title={
<span
style={{
color: theme.palette.text.primary,
fontSize: '14px',
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor:
theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(
payment?.creatorAddress
);
}}
>
{formatAddress(payment?.creatorAddress)}
</ButtonBase>
</Tooltip>
</TableCell>
<TableCell>
<Tooltip
title={
<span
style={{
color: theme.palette.text.primary,
fontSize: '14px',
fontWeight: 700,
}}
>
copy address
</span>
}
placement="bottom"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor:
theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(payment?.recipient);
}}
>
{formatAddress(payment?.recipient)}
</ButtonBase>
</Tooltip>
</TableCell>
<TableCell>{payment?.amount}</TableCell>
<TableCell>
{formatTimestamp(payment?.timestamp)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
)}
</Box>
</Box>
</DrawerUserLookup>

View File

@ -1,10 +1,17 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { Popover, Button, Box, CircularProgress } from '@mui/material';
import { useCallback, useContext, useEffect, useState } from 'react';
import {
Popover,
Button,
Box,
CircularProgress,
useTheme,
} from '@mui/material';
import { executeEvent } from '../utils/events';
import { MyContext } from '../App';
export const WrapperUserAction = ({ children, address, name, disabled }) => {
const {isRunningPublicNode} = useContext(MyContext)
const theme = useTheme();
const { isRunningPublicNode } = useContext(MyContext);
const [anchorEl, setAnchorEl] = useState(null);
// Handle child element click to open Popover
@ -22,8 +29,8 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
const open = Boolean(anchorEl);
const id = open ? address || name : undefined;
if(disabled){
return children
if (disabled) {
return children;
}
return (
@ -31,16 +38,16 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
<Box
onClick={handleChildClick} // Open popover on click
sx={{
display: 'inline-flex', // Keep inline behavior
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'flex-start', // Prevent stretching to parent height
cursor: 'pointer',
display: 'inline-flex', // Keep inline behavior
height: 'fit-content', // Limit height to content size
justifyContent: 'center',
maxHeight: '100%', // Prevent flex shrink behavior in a flex container
maxWidth: '100%', // Optional: Limit the width to avoid overflow
padding: 0,
width: 'fit-content', // Limit width to content size
height: 'fit-content', // Limit height to content size
alignSelf: 'flex-start', // Prevent stretching to parent height
maxWidth: '100%', // Optional: Limit the width to avoid overflow
maxHeight: '100%', // Prevent flex shrink behavior in a flex container
}}
>
{/* Render the child without altering dimensions */}
@ -49,159 +56,151 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
{/* Popover */}
{open && (
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
componentsProps={{
paper: {
onClick: (event) => event.stopPropagation(), // Stop propagation inside popover
},
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Option 1: Message */}
<Button
variant="text"
onClick={() => {
handleClose();
setTimeout(() => {
executeEvent('openDirectMessageInternal', {
address,
name,
});
}, 200);
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Message
</Button>
{/* Option 2: Send QORT */}
<Button
variant="text"
onClick={() => {
executeEvent('openPaymentInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Send QORT
</Button>
<Button
variant="text"
onClick={() => {
navigator.clipboard.writeText(address|| "");
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Copy address
</Button>
<Button
variant="text"
onClick={() => {
executeEvent('openUserLookupDrawer', {
addressOrName: name || address
})
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
componentsProps={{
paper: {
onClick: (event) => event.stopPropagation(), // Stop propagation inside popover
},
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Option 1: Message */}
<Button
variant="text"
onClick={() => {
handleClose();
setTimeout(() => {
executeEvent('openDirectMessageInternal', {
address,
name,
});
}, 200);
}}
sx={{
color: theme.palette.text.primary,
justifyContent: 'flex-start',
}}
>
Message
</Button>
{/* Option 2: Send QORT */}
<Button
variant="text"
onClick={() => {
executeEvent('openPaymentInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: theme.palette.text.primary,
justifyContent: 'flex-start',
}}
>
Send QORT
</Button>
<Button
variant="text"
onClick={() => {
navigator.clipboard.writeText(address || '');
handleClose();
}}
sx={{
color: theme.palette.text.primary,
justifyContent: 'flex-start',
}}
>
Copy address
</Button>
<Button
variant="text"
onClick={() => {
executeEvent('openUserLookupDrawer', {
addressOrName: name || address,
});
handleClose();
}}
sx={{
color: theme.palette.text.primary,
justifyContent: 'flex-start',
}}
>
User lookup
</Button>
</Button>
{!isRunningPublicNode && (
<BlockUser handleClose={handleClose} address={address} name={name} />
)}
</Box>
</Popover>
{!isRunningPublicNode && (
<BlockUser
handleClose={handleClose}
address={address}
name={name}
/>
)}
</Box>
</Popover>
)}
</>
);
};
const BlockUser = ({ address, name, handleClose }) => {
const [isAlreadyBlocked, setIsAlreadyBlocked] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const { isUserBlocked, addToBlockList, removeBlockFromList } =
useContext(MyContext);
const theme = useTheme();
const BlockUser = ({address, name, handleClose})=> {
const [isAlreadyBlocked, setIsAlreadyBlocked] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const {isUserBlocked,
addToBlockList,
removeBlockFromList} = useContext(MyContext)
useEffect(()=> {
if(!address) return
setIsAlreadyBlocked(isUserBlocked(address, name))
}, [address, setIsAlreadyBlocked, isUserBlocked, name])
useEffect(() => {
if (!address) return;
setIsAlreadyBlocked(isUserBlocked(address, name));
}, [address, setIsAlreadyBlocked, isUserBlocked, name]);
return (
<Button
variant="text"
onClick={async () => {
try {
setIsLoading(true)
executeEvent("blockUserFromOutside", {
user: address
})
// if(isAlreadyBlocked === true){
// await removeBlockFromList(address, name)
// } else if(isAlreadyBlocked === false) {
// await addToBlockList(address, name)
// }
// executeEvent('updateChatMessagesWithBlocks', true)
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
handleClose();
}
}}
sx={{
color: 'white',
variant="text"
onClick={async () => {
try {
setIsLoading(true);
executeEvent('blockUserFromOutside', {
user: address,
});
// if(isAlreadyBlocked === true){
// await removeBlockFromList(address, name)
// } else if(isAlreadyBlocked === false) {
// await addToBlockList(address, name)
// }
// executeEvent('updateChatMessagesWithBlocks', true)
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
handleClose();
}
}}
sx={{
color: theme.palette.text.primary,
gap: '10px',
justifyContent: 'flex-start',
gap: '10px'
}}
>
{(isAlreadyBlocked === null || isLoading) && (
<CircularProgress color="secondary" size={24} />
)}
{isAlreadyBlocked && (
'Unblock name'
)}
{isAlreadyBlocked === false && (
'Block name'
)}
</Button>
)
}
}}
>
{(isAlreadyBlocked === null || isLoading) && (
<CircularProgress color="secondary" size={24} />
)}
{isAlreadyBlocked && 'Unblock name'}
{isAlreadyBlocked === false && 'Block name'}
</Button>
);
};