mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-23 19:37:52 +00:00
user lookup feature
This commit is contained in:
parent
f9b1e2784f
commit
8af3977178
50
src/App.tsx
50
src/App.tsx
@ -35,6 +35,7 @@ import RefreshIcon from "@mui/icons-material/Refresh";
|
|||||||
import Logo2 from "./assets/svgs/Logo2.svg";
|
import Logo2 from "./assets/svgs/Logo2.svg";
|
||||||
import Copy from "./assets/svgs/Copy.svg";
|
import Copy from "./assets/svgs/Copy.svg";
|
||||||
import ltcLogo from "./assets/ltc.png";
|
import ltcLogo from "./assets/ltc.png";
|
||||||
|
import PersonSearchIcon from '@mui/icons-material/PersonSearch';
|
||||||
import qortLogo from "./assets/qort.png";
|
import qortLogo from "./assets/qort.png";
|
||||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||||
import Download from "./assets/svgs/Download.svg";
|
import Download from "./assets/svgs/Download.svg";
|
||||||
@ -113,6 +114,7 @@ import {
|
|||||||
canSaveSettingToQdnAtom,
|
canSaveSettingToQdnAtom,
|
||||||
enabledDevModeAtom,
|
enabledDevModeAtom,
|
||||||
fullScreenAtom,
|
fullScreenAtom,
|
||||||
|
groupsPropertiesAtom,
|
||||||
hasSettingsChangedAtom,
|
hasSettingsChangedAtom,
|
||||||
isDisabledEditorEnterAtom,
|
isDisabledEditorEnterAtom,
|
||||||
isUsingImportExportSettingsAtom,
|
isUsingImportExportSettingsAtom,
|
||||||
@ -145,6 +147,8 @@ import { QMailStatus } from "./components/QMailStatus";
|
|||||||
import { GlobalActions } from "./components/GlobalActions/GlobalActions";
|
import { GlobalActions } from "./components/GlobalActions/GlobalActions";
|
||||||
import { useBlockedAddresses } from "./components/Group/useBlockUsers";
|
import { useBlockedAddresses } from "./components/Group/useBlockUsers";
|
||||||
import { WalletIcon } from "./assets/Icons/WalletIcon";
|
import { WalletIcon } from "./assets/Icons/WalletIcon";
|
||||||
|
import { DrawerUserLookup } from "./components/Drawer/DrawerUserLookup";
|
||||||
|
import { UserLookup } from "./components/UserLookup.tsx/UserLookup";
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
| "not-authenticated"
|
| "not-authenticated"
|
||||||
@ -400,6 +404,7 @@ function App() {
|
|||||||
const [openSnack, setOpenSnack] = useState(false);
|
const [openSnack, setOpenSnack] = useState(false);
|
||||||
const [hasLocalNode, setHasLocalNode] = useState(false);
|
const [hasLocalNode, setHasLocalNode] = useState(false);
|
||||||
const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false);
|
const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false);
|
||||||
|
const [isOpenDrawerLookup, setIsOpenDrawerLookup] = useState(false)
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [isOpenSendQort, setIsOpenSendQort] = useState(false);
|
const [isOpenSendQort, setIsOpenSendQort] = useState(false);
|
||||||
const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false);
|
const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false);
|
||||||
@ -488,7 +493,7 @@ function App() {
|
|||||||
const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom);
|
const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom);
|
||||||
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
|
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
|
||||||
const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
|
const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
|
||||||
|
const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom)
|
||||||
const resetAllRecoil = () => {
|
const resetAllRecoil = () => {
|
||||||
resetAtomSortablePinnedAppsAtom();
|
resetAtomSortablePinnedAppsAtom();
|
||||||
resetAtomCanSaveSettingToQdnAtom();
|
resetAtomCanSaveSettingToQdnAtom();
|
||||||
@ -498,6 +503,8 @@ function App() {
|
|||||||
resetAtomIsUsingImportExportSettingsAtom()
|
resetAtomIsUsingImportExportSettingsAtom()
|
||||||
resetAtomQMailLastEnteredTimestampAtom()
|
resetAtomQMailLastEnteredTimestampAtom()
|
||||||
resetAtomMailsAtom()
|
resetAtomMailsAtom()
|
||||||
|
resetGroupPropertiesAtom()
|
||||||
|
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMobile) return;
|
if (!isMobile) return;
|
||||||
@ -1696,6 +1703,38 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
<ButtonBase
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenDrawerLookup(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
title={<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>USER LOOKUP</span>}
|
||||||
|
placement="left"
|
||||||
|
arrow
|
||||||
|
sx={{ fontSize: "24" }}
|
||||||
|
slotProps={{
|
||||||
|
tooltip: {
|
||||||
|
sx: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
sx: {
|
||||||
|
color: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PersonSearchIcon
|
||||||
|
sx={{
|
||||||
|
color: "rgba(255, 255, 255, 0.5)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</ButtonBase>
|
||||||
|
|
||||||
{desktopViewMode !== 'home' && (
|
{desktopViewMode !== 'home' && (
|
||||||
<>
|
<>
|
||||||
@ -2055,7 +2094,7 @@ function App() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
zIndex: 6,
|
zIndex: 10000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Spacer height="22px" />
|
<Spacer height="22px" />
|
||||||
@ -3110,7 +3149,7 @@ function App() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
zIndex: 6,
|
zIndex: 10000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Spacer height="48px" />
|
<Spacer height="48px" />
|
||||||
@ -3210,6 +3249,9 @@ function App() {
|
|||||||
open={isShow}
|
open={isShow}
|
||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
|
sx={{
|
||||||
|
zIndex: 10001
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle id="alert-dialog-title">{message.paymentFee ? "Payment" : "Publish"}</DialogTitle>
|
<DialogTitle id="alert-dialog-title">{message.paymentFee ? "Payment" : "Publish"}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@ -3635,7 +3677,7 @@ function App() {
|
|||||||
>
|
>
|
||||||
{renderProfileLeft()}
|
{renderProfileLeft()}
|
||||||
</DrawerComponent>
|
</DrawerComponent>
|
||||||
|
<UserLookup isOpenDrawerLookup={isOpenDrawerLookup} setIsOpenDrawerLookup={setIsOpenDrawerLookup} />
|
||||||
</GlobalContext.Provider>
|
</GlobalContext.Provider>
|
||||||
{extState === "create-wallet" && walletToBeDownloaded && (
|
{extState === "create-wallet" && walletToBeDownloaded && (
|
||||||
<ButtonBase onClick={()=> {
|
<ButtonBase onClick={()=> {
|
||||||
|
@ -169,4 +169,9 @@ export const qMailLastEnteredTimestampAtom = atom({
|
|||||||
export const mailsAtom = atom({
|
export const mailsAtom = atom({
|
||||||
key: 'mailsAtom',
|
key: 'mailsAtom',
|
||||||
default: [],
|
default: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const groupsPropertiesAtom = atom({
|
||||||
|
key: 'groupsPropertiesAtom',
|
||||||
|
default: {},
|
||||||
});
|
});
|
@ -845,7 +845,7 @@ export async function getNameInfoForOthers(address) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getAddressInfo(address) {
|
export async function getAddressInfo(address) {
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + "/addresses/" + address);
|
const response = await fetch(validApi + "/addresses/" + address);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
@ -18,7 +18,7 @@ import {
|
|||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
||||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
import { groupsPropertiesAtom, myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
||||||
import { Label } from "../Group/AddGroup";
|
import { Label } from "../Group/AddGroup";
|
||||||
import { Spacer } from "../../common/Spacer";
|
import { Spacer } from "../../common/Spacer";
|
||||||
import {
|
import {
|
||||||
@ -43,14 +43,22 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
const [logo, setLogo] = useState(null);
|
const [logo, setLogo] = useState(null);
|
||||||
const [qortalUrl, setQortalUrl] = useState("");
|
const [qortalUrl, setQortalUrl] = useState("");
|
||||||
const [selectedGroup, setSelectedGroup] = useState(0);
|
const [selectedGroup, setSelectedGroup] = useState(0);
|
||||||
|
const [groupsProperties] = useRecoilState(groupsPropertiesAtom)
|
||||||
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
|
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
|
||||||
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
|
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
|
||||||
myGroupsWhereIAmAdminAtom
|
myGroupsWhereIAmAdminAtom
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const myGroupsWhereIAmAdmin = useMemo(()=> {
|
||||||
|
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||||
|
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
|
||||||
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
|
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
|
||||||
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext);
|
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext);
|
||||||
|
|
||||||
|
|
||||||
|
const myGroupsPrivate = useMemo(()=> {
|
||||||
|
return memberGroups?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||||
|
}, [memberGroups, groupsProperties])
|
||||||
const [privateAppValues, setPrivateAppValues] = useState({
|
const [privateAppValues, setPrivateAppValues] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
@ -95,7 +103,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
|
|
||||||
await openApp(privateAppValues, true);
|
await openApp(privateAppValues, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error', error?.message)
|
console.error(error)
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -311,7 +319,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
>
|
>
|
||||||
<MenuItem value={0}>No group selected</MenuItem>
|
<MenuItem value={0}>No group selected</MenuItem>
|
||||||
|
|
||||||
{memberGroups
|
{myGroupsPrivate
|
||||||
?.filter((item) => !item?.isOpen)
|
?.filter((item) => !item?.isOpen)
|
||||||
.map((group) => {
|
.map((group) => {
|
||||||
return (
|
return (
|
||||||
|
@ -194,7 +194,7 @@ export const GroupAnnouncements = ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.error("error", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
22
src/components/Drawer/DrawerUserLookup.tsx
Normal file
22
src/components/Drawer/DrawerUserLookup.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Drawer from '@mui/material/Drawer';
|
||||||
|
|
||||||
|
export const DrawerUserLookup = ({open, setOpen, children}) => {
|
||||||
|
|
||||||
|
const toggleDrawer = (newOpen: boolean) => () => {
|
||||||
|
setOpen(newOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Drawer disableEnforceFocus hideBackdrop={true} open={open} onClose={toggleDrawer(false)}>
|
||||||
|
<Box sx={{ width: '70vw', height: '100%', maxWidth: '1000px' }} role="presentation">
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -74,8 +74,8 @@ import { HubsIcon } from "../../assets/Icons/HubsIcon";
|
|||||||
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
|
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
|
||||||
import { formatEmailDate } from "./QMailMessages";
|
import { formatEmailDate } from "./QMailMessages";
|
||||||
import { AdminSpace } from "../Chat/AdminSpace";
|
import { AdminSpace } from "../Chat/AdminSpace";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
import { addressInfoControllerAtom, selectedGroupIdAtom } from "../../atoms/global";
|
import { addressInfoControllerAtom, groupsPropertiesAtom, selectedGroupIdAtom } from "../../atoms/global";
|
||||||
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
||||||
import BlockIcon from '@mui/icons-material/Block';
|
import BlockIcon from '@mui/icons-material/Block';
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
@ -446,7 +446,7 @@ export const Group = ({
|
|||||||
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
||||||
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
||||||
|
|
||||||
const [groupsProperties, setGroupsProperties] = useState({})
|
const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom)
|
||||||
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
|
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
|
||||||
|
|
||||||
const isPrivate = useMemo(()=> {
|
const isPrivate = useMemo(()=> {
|
||||||
|
@ -1,209 +1,257 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { getBaseApiReact } from '../../App';
|
import { getBaseApiReact } from "../../App";
|
||||||
import { Box, Tooltip, Typography } from '@mui/material';
|
import { Box, Tooltip, Typography } from "@mui/material";
|
||||||
import { BarSpinner } from '../../common/Spinners/BarSpinner/BarSpinner';
|
import { BarSpinner } from "../../common/Spinners/BarSpinner/BarSpinner";
|
||||||
|
import { formatDate } from "../../utils/time";
|
||||||
|
|
||||||
function getAverageLtcPerQort(trades) {
|
function getAverageLtcPerQort(trades) {
|
||||||
let totalQort = 0;
|
let totalQort = 0;
|
||||||
let totalLtc = 0;
|
let totalLtc = 0;
|
||||||
|
|
||||||
trades.forEach((trade) => {
|
|
||||||
const qort = parseFloat(trade.qortAmount);
|
|
||||||
const ltc = parseFloat(trade.foreignAmount);
|
|
||||||
|
|
||||||
totalQort += qort;
|
|
||||||
totalLtc += ltc;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Avoid division by zero
|
|
||||||
if (totalQort === 0) return 0;
|
|
||||||
|
|
||||||
// Weighted average price
|
|
||||||
return parseFloat((totalLtc / totalQort).toFixed(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTwoWeeksAgoTimestamp() {
|
trades.forEach((trade) => {
|
||||||
const now = new Date();
|
const qort = parseFloat(trade.qortAmount);
|
||||||
now.setDate(now.getDate() - 14); // Subtract 14 days
|
const ltc = parseFloat(trade.foreignAmount);
|
||||||
return now.getTime(); // Get timestamp in milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatWithCommasAndDecimals(number) {
|
|
||||||
|
|
||||||
return Number(number).toLocaleString();
|
totalQort += qort;
|
||||||
}
|
totalLtc += ltc;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Avoid division by zero
|
||||||
|
if (totalQort === 0) return 0;
|
||||||
|
|
||||||
|
// Weighted average price
|
||||||
|
return parseFloat((totalLtc / totalQort).toFixed(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTwoWeeksAgoTimestamp() {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 14); // Subtract 14 days
|
||||||
|
return now.getTime(); // Get timestamp in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatWithCommasAndDecimals(number) {
|
||||||
|
return Number(number).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const QortPrice = () => {
|
export const QortPrice = () => {
|
||||||
const [ltcPerQort, setLtcPerQort] = useState(null)
|
const [ltcPerQort, setLtcPerQort] = useState(null);
|
||||||
const [supply, setSupply] = useState(null)
|
const [supply, setSupply] = useState(null);
|
||||||
const [lastBlock, setLastBlock] = useState(null)
|
const [lastBlock, setLastBlock] = useState(null);
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const getPrice = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
const response = await fetch(`${getBaseApiReact()}/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${getTwoWeeksAgoTimestamp()}&limit=20&reverse=true`);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
|
|
||||||
setLtcPerQort(getAverageLtcPerQort(data));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const getLastBlock = useCallback(async () => {
|
const getPrice = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
|
|
||||||
const response = await fetch(`${getBaseApiReact()}/blocks/last`);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
setLastBlock(data);
|
const response = await fetch(
|
||||||
} catch (error) {
|
`${getBaseApiReact()}/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${getTwoWeeksAgoTimestamp()}&limit=20&reverse=true`
|
||||||
console.error(error);
|
);
|
||||||
} finally {
|
const data = await response.json();
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const getSupplyInCirculation = useCallback(async () => {
|
setLtcPerQort(getAverageLtcPerQort(data));
|
||||||
try {
|
} catch (error) {
|
||||||
setLoading(true)
|
console.error(error);
|
||||||
|
} finally {
|
||||||
const response = await fetch(`${getBaseApiReact()}/stats/supply/circulating`);
|
setLoading(false);
|
||||||
const data = await response.text();
|
}
|
||||||
formatWithCommasAndDecimals(data)
|
}, []);
|
||||||
setSupply(formatWithCommasAndDecimals(data));
|
|
||||||
} catch (error) {
|
const getLastBlock = useCallback(async () => {
|
||||||
console.error(error);
|
try {
|
||||||
} finally {
|
setLoading(true);
|
||||||
setLoading(false)
|
|
||||||
|
const response = await fetch(`${getBaseApiReact()}/blocks/last`);
|
||||||
}
|
const data = await response.json();
|
||||||
}, [])
|
|
||||||
|
setLastBlock(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getSupplyInCirculation = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${getBaseApiReact()}/stats/supply/circulating`
|
||||||
|
);
|
||||||
|
const data = await response.text();
|
||||||
|
formatWithCommasAndDecimals(data);
|
||||||
|
setSupply(formatWithCommasAndDecimals(data));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPrice();
|
||||||
|
getSupplyInCirculation();
|
||||||
|
getLastBlock();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
getPrice();
|
||||||
|
getSupplyInCirculation();
|
||||||
|
getLastBlock();
|
||||||
|
}, 900000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [getPrice]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
getPrice();
|
|
||||||
getSupplyInCirculation()
|
|
||||||
getLastBlock()
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
getPrice();
|
|
||||||
getSupplyInCirculation()
|
|
||||||
getLastBlock()
|
|
||||||
}, 900000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
|
|
||||||
}, [getPrice]);
|
|
||||||
|
|
||||||
console.log('supply', supply)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
|
||||||
gap: "20px",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: "322px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
title={<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>Based on the latest 20 trades</span>}
|
|
||||||
placement="bottom"
|
|
||||||
arrow
|
|
||||||
sx={{ fontSize: "24" }}
|
|
||||||
slotProps={{
|
|
||||||
tooltip: {
|
|
||||||
sx: {
|
|
||||||
color: "#ffffff",
|
|
||||||
backgroundColor: "#444444",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
arrow: {
|
|
||||||
sx: {
|
|
||||||
color: "#444444",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{
|
|
||||||
width: "322px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
gap: "20px",
|
||||||
gap: '10px',
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "322px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>
|
||||||
|
Based on the latest 20 trades
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placement="bottom"
|
||||||
|
arrow
|
||||||
|
sx={{ fontSize: "24" }}
|
||||||
|
slotProps={{
|
||||||
|
tooltip: {
|
||||||
|
sx: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
sx: {
|
||||||
|
color: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "322px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "10px",
|
||||||
|
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between",
|
||||||
}}>
|
}}
|
||||||
<Typography sx={{
|
>
|
||||||
fontSize: "1rem",
|
<Typography
|
||||||
fontWeight: 'bold'
|
sx={{
|
||||||
}}>Price</Typography>
|
|
||||||
{!ltcPerQort ? (
|
|
||||||
<BarSpinner width="16px" color="white" />
|
|
||||||
): (
|
|
||||||
<Typography sx={{
|
|
||||||
fontSize: "1rem",
|
fontSize: "1rem",
|
||||||
}}>{ltcPerQort} LTC/QORT</Typography>
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Price
|
||||||
|
</Typography>
|
||||||
|
{!ltcPerQort ? (
|
||||||
|
<BarSpinner width="16px" color="white" />
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ltcPerQort} LTC/QORT
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "322px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "10px",
|
||||||
|
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Supply
|
||||||
|
</Typography>
|
||||||
|
{!supply ? (
|
||||||
|
<BarSpinner width="16px" color="white" />
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{supply} QORT
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>
|
||||||
|
{lastBlock?.timestamp && formatDate(lastBlock?.timestamp)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placement="bottom"
|
||||||
|
arrow
|
||||||
|
sx={{ fontSize: "24" }}
|
||||||
|
slotProps={{
|
||||||
|
tooltip: {
|
||||||
|
sx: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
sx: {
|
||||||
|
color: "#444444",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "322px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "10px",
|
||||||
|
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<Typography
|
||||||
</Box>
|
sx={{
|
||||||
</Tooltip>
|
|
||||||
<Box sx={{
|
|
||||||
width: "322px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: '10px',
|
|
||||||
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
}}>
|
|
||||||
<Typography sx={{
|
|
||||||
fontSize: "1rem",
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}>Supply</Typography>
|
|
||||||
{!supply ? (
|
|
||||||
<BarSpinner width="16px" color="white" />
|
|
||||||
): (
|
|
||||||
<Typography sx={{
|
|
||||||
fontSize: "1rem",
|
fontSize: "1rem",
|
||||||
}}>{supply} QORT</Typography>
|
fontWeight: "bold",
|
||||||
)}
|
}}
|
||||||
|
>
|
||||||
</Box>
|
Last height
|
||||||
<Box sx={{
|
</Typography>
|
||||||
width: "322px",
|
{!lastBlock?.height ? (
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: '10px',
|
|
||||||
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
}}>
|
|
||||||
<Typography sx={{
|
|
||||||
fontSize: "1rem",
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}>Last height</Typography>
|
|
||||||
{!lastBlock?.height ? (
|
|
||||||
<BarSpinner width="16px" color="white" />
|
<BarSpinner width="16px" color="white" />
|
||||||
): (
|
) : (
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: "1rem",
|
sx={{
|
||||||
}}>{lastBlock?.height}</Typography>
|
fontSize: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{lastBlock?.height}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
495
src/components/UserLookup.tsx/UserLookup.tsx
Normal file
495
src/components/UserLookup.tsx/UserLookup.tsx
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { DrawerUserLookup } from "../Drawer/DrawerUserLookup";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ButtonBase,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TextField,
|
||||||
|
Tooltip,
|
||||||
|
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";
|
||||||
|
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||||
|
|
||||||
|
function formatAddress(str) {
|
||||||
|
if (str.length <= 12) return str;
|
||||||
|
|
||||||
|
const first6 = str.slice(0, 6);
|
||||||
|
const last6 = str.slice(-6);
|
||||||
|
|
||||||
|
return `${first6}....${last6}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}, [nameOrAddress]);
|
||||||
|
|
||||||
|
const openUserLookupDrawerFunc = useCallback((e) => {
|
||||||
|
setIsOpenDrawerLookup(true)
|
||||||
|
const message = e.detail?.addressOrName;
|
||||||
|
if(message){
|
||||||
|
lookupFunc(message)
|
||||||
|
}
|
||||||
|
}, [lookupFunc, setIsOpenDrawerLookup]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
subscribeToEvent("openUserLookupDrawer", openUserLookupDrawerFunc);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribeFromEvent("openUserLookupDrawer", openUserLookupDrawerFunc);
|
||||||
|
};
|
||||||
|
}, [openUserLookupDrawerFunc]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DrawerUserLookup open={isOpenDrawerLookup} setOpen={setIsOpenDrawerLookup}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: "15px",
|
||||||
|
height: "100vh",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "5px",
|
||||||
|
alignItems: "center",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
value={nameOrAddress}
|
||||||
|
onChange={(e) => setNameOrAddress(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
placeholder="Address or Name"
|
||||||
|
autoComplete="off"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && nameOrAddress) {
|
||||||
|
lookupFunc();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ButtonBase onClick={lookupFunc} >
|
||||||
|
<SearchIcon sx={{
|
||||||
|
color: 'white',
|
||||||
|
marginRight: '20px'
|
||||||
|
}} />
|
||||||
|
</ButtonBase>
|
||||||
|
<ButtonBase sx={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
|
||||||
|
}} onClick={()=> {
|
||||||
|
setIsOpenDrawerLookup(false)
|
||||||
|
}}>
|
||||||
|
<CloseFullscreenIcon sx={{
|
||||||
|
color: 'white'
|
||||||
|
}} />
|
||||||
|
</ButtonBase>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flexGrow: 1,
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!isLoadingUser && errorMessage && (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: '40px'
|
||||||
|
}}>
|
||||||
|
<Typography>{errorMessage}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{isLoadingUser && (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: '40px'
|
||||||
|
}}>
|
||||||
|
<CircularProgress sx={{
|
||||||
|
color: 'white'
|
||||||
|
}} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{!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)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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: "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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</DrawerUserLookup>
|
||||||
|
);
|
||||||
|
};
|
@ -121,6 +121,42 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
|
|||||||
>
|
>
|
||||||
Copy address
|
Copy address
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
executeEvent('openPaymentInternal', {
|
||||||
|
address,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send QORT
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
executeEvent('openUserLookupDrawer', {
|
||||||
|
addressOrName: name || address
|
||||||
|
})
|
||||||
|
handleClose();
|
||||||
|
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
User lookup
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
<BlockUser handleClose={handleClose} address={address} name={name} />
|
<BlockUser handleClose={handleClose} address={address} name={name} />
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -12,7 +12,7 @@ export function formatTimestamp(timestamp: number): string {
|
|||||||
} else if (elapsedTime < 1440) {
|
} else if (elapsedTime < 1440) {
|
||||||
return `${Math.floor(elapsedTime / 60)}h ago`
|
return `${Math.floor(elapsedTime / 60)}h ago`
|
||||||
} else {
|
} else {
|
||||||
return timestampMoment.format('MMM D')
|
return timestampMoment.format('MMM D, YYYY')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function formatTimestampForum(timestamp: number): string {
|
export function formatTimestampForum(timestamp: number): string {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user