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

View File

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

View File

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