visual sugar during the bridge transaction added

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-08-25 18:07:51 +03:00
parent 0a3786b85e
commit cec3521f39
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
3 changed files with 209 additions and 48 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.2.14",
"version": "0.2.15",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -97,7 +97,7 @@ const NavContent = ({ chainId, addressChainId }) => {
{ghstSymbol} Price: {formatCurrency(ghstPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
GHOST Supply: {formatCurrency(ghostedSupplyPrice, 2)}
GHOSTed Supply: {formatCurrency(ghostedSupplyPrice, 2)}
</Box>
</Box>
</Box>

View File

@ -21,6 +21,7 @@ import { useBlockNumber, useTransactionConfirmations } from "wagmi";
import { keccak256 } from "viem";
import { u64, u128 } from "scale-ts";
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import PendingIcon from '@mui/icons-material/Pending';
import PendingActionsIcon from '@mui/icons-material/PendingActions';
import ArrowBack from '@mui/icons-material/ArrowBack';
@ -28,6 +29,7 @@ import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
import ThumbDownAltIcon from '@mui/icons-material/ThumbDownAlt';
import HandshakeIcon from '@mui/icons-material/Handshake';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CheckIcon from '@mui/icons-material/Check';
@ -38,6 +40,7 @@ import SwapCollection from "../../components/Swap/SwapCollection";
import TokenStack from "../../components/TokenStack/TokenStack";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import Modal from "../../components/Modal/Modal";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import { PrimaryButton } from "../../components/Button";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
@ -66,6 +69,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const [copiedIndex, setCopiedIndex] = useState(null);
const [isPending, setIsPending] = useState(false);
const [bridgeAction, setBridgeAction] = useState(true);
const [activeTxIndex, setActiveTxIndex] = useState(-1);
@ -77,7 +81,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
return string.slice(0, first) + "..." + string.slice(second);
}
const initialStoredTransactions = sessionStorage.getItem(STORAGE_PREFIX);
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
// const initialStoredTransactions = JSON.stringify([
// {
// sessionIndex: 124,
@ -177,9 +181,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
}, [config]);
const currentRecord = useMemo(() => {
if (activeTxIndex === -1) return undefined
const current = storedTransactions.at(activeTxIndex)
if (!current) return undefined
if (!watchTransaction) return undefined
const finalization = Math.max(0, (finalityDelay + watchTransaction.blockNumber) - Number(blockNumber));
const receivedClapsLength = receivedClaps?.length ?? 0;
@ -233,10 +235,17 @@ const Bridge = ({ chainId, address, config, connect }) => {
const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions);
sessionStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
const copyToClipboard = (text, index) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null) , 800);
});
};
const ghostOrConnect = async () => {
if (address === "") {
connect();
@ -262,10 +271,13 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [...storedTransactions, transaction];
setStoredTransactions(newStoredTransactions);
sessionStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
if (providerDetail) {
setActiveTxIndex(newStoredTransactions.length - 1)
}
}
}
return (
<Box height="calc(100vh - 43px)">
@ -332,16 +344,18 @@ const Bridge = ({ chainId, address, config, connect }) => {
alignItems="center"
>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
sx={{
width: "35px",
height: "35px",
transition: "transform 0.5s ease-in-out",
transform: (currentRecord?.finalization ?? 0) % 2 === 0 ? "rotate(0deg)" : "rotate(180deg)"
}}
viewBox="0 0 25 25"
component={HourglassBottomIcon}
/>
<Typography variant="caption">Finalization</Typography>
<Typography variant="caption">
{Math.max(
0,
(finalityDelay + (currentRecord ? currentRecord.blockNumber : 0) - Number(blockNumber))
).toString()} blocks left
{(currentRecord?.finalization ?? 0).toString()} blocks left
</Typography>
</Box>
@ -364,16 +378,46 @@ const Bridge = ({ chainId, address, config, connect }) => {
alignItems="center"
>
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center">
{currentRecord?.step <= 1
? (
<>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
sx={{
width: "35px",
height: "35px",
transition: "transform 0.5s ease-in-out",
transform: currentRecord?.step === 1 && Number(blockNumber) % 2 === 0
? "rotateX(0deg)"
: "rotateX(180deg)"
}}
viewBox="0 0 25 25"
component={ThumbUpIcon}
/>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
sx={{
width: "35px",
height: "35px",
transition: "transform 0.5s ease-in-out",
transform: currentRecord?.step === 1 && Number(blockNumber) % 2 === 0
? "rotateX(0deg)"
: "rotateX(180deg)"
}}
viewBox="0 0 25 25"
component={ThumbDownAltIcon}
/>
</>
)
: (
<GhostStyledIcon
sx={{
width: "35px",
height: "35px",
}}
viewBox="0 0 25 25"
component={HandshakeIcon}
/>
)
}
</Box>
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center">
<Typography variant="caption">Slow Claps</Typography>
@ -416,7 +460,55 @@ const Bridge = ({ chainId, address, config, connect }) => {
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Box display="flex" flexDirection="row">
<Typography variant="body2">Session Index:</Typography>
<InfoTooltip message={<>
<Typography variant="subtitle1">Transaction Watchmen</Typography>
<Box
width="280px"
height="250px"
margin="0"
padding="0"
marginTop="5px"
display="flex"
flexDirection="column"
sx={{ overflowY: "auto" }}
>
{authorities?.map((authority, idx) => {
const authorityAddress = ss58Address(authority.asHex(), 1996);
const disabled = clapsInSession?.at(idx)?.at(1)?.disabled;
const clapped = receivedClaps?.some(authId => authId === idx);
return (
<Box
key={idx}
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
>
<Typography
sx={{
fontSize: "10px",
width: "275px",
overflow: 'hidden',
whiteSpace: 'normal',
textOverflow: "ellipsis",
color: clapped
? theme.colors.primary[300]
: clapped
? theme.colors.feedback.error
: theme.colors.gray[10]
}}
>
{authorityAddress}
</Typography>
</Box>
)
})}
</Box>
</>} />
</Box>
<Typography variant="body2">{currentRecord?.sessionIndex}</Typography>
</Box>
<Box
@ -427,11 +519,17 @@ const Bridge = ({ chainId, address, config, connect }) => {
>
<Typography variant="body2">Receiver Address:</Typography>
<Link
onClick={() => navigator.clipboard.writeText(currentRecord ? currentRecord.receiverAddress : "")}
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
onClick={() => copyToClipboard(currentRecord ? currentRecord.receiverAddress : "", 0)}
>
<Typography variant="body2">{
currentRecord ? sliceString(currentRecord.receiverAddress, 14, -5) : ""
}</Typography>
<Typography variant="body2">
{currentRecord ? sliceString(currentRecord.receiverAddress, 14, -5) : ""}
</Typography>
<GhostStyledIcon
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
viewBox="0 0 25 25"
component={copiedIndex === 0 ? CheckIcon : ContentPasteIcon}
/>
</Link>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between">
@ -453,21 +551,36 @@ const Bridge = ({ chainId, address, config, connect }) => {
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Transaction Hash:</Typography>
<Link
onClick={() => navigator.clipboard.writeText(currentRecord ? currentRecord.transactionHash : "")}
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
onClick={() => copyToClipboard(currentRecord ? currentRecord.transactionHash : "", 1)}
>
<Typography variant="body2">{
currentRecord ? sliceString(currentRecord.transactionHash, 10, -8) : "0x"
}</Typography>
<Typography variant="body2">
{currentRecord ? sliceString(currentRecord.transactionHash, 10, -8) : "0x"}
</Typography>
<GhostStyledIcon
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
viewBox="0 0 25 25"
component={copiedIndex === 1 ? CheckIcon : ContentPasteIcon}
/>
</Link>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Box display="flex" flexDirection="row">
<Typography variant="body2">Arguments Hash:</Typography>
<InfoTooltip message="A unique identifier for transaction parameters, represented as a hash generated by keccak256(receiver, amount, chainId)." />
</Box>
<Link
onClick={() => navigator.clipboard.writeText(hashedArguments ? hashedArguments : "")}
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
onClick={() => copyToClipboard(hashedArguments ? hashedArguments : "", 2)}
>
<Typography variant="body2">{
hashedArguments ? sliceString(hashedArguments, 10, -8) : "0x"
}</Typography>
<Typography variant="body2">
{hashedArguments ? sliceString(hashedArguments, 10, -8) : "0x"}
</Typography>
<GhostStyledIcon
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
viewBox="0 0 25 25"
component={copiedIndex === 2 ? CheckIcon : ContentPasteIcon}
/>
</Link>
</Box>
</Box>
@ -499,7 +612,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
onClick={() => setBridgeAction(!bridgeAction)}
/>)}
<Typography variant="h4">{
bridgeAction ? `Bridge $${ghstSymbol}` : "Transaction history"
bridgeAction ? `Bridge $${ghstSymbol}` : "Transaction History"
}</Typography>
</Box>
}
@ -509,7 +622,6 @@ const Bridge = ({ chainId, address, config, connect }) => {
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
onClick={() => setBridgeAction(!bridgeAction)}
/>)}
enableBackground
fullWidth
>
@ -578,6 +690,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
<em>
GHOST Wallet is not detected on your browser. Download&nbsp;
<Link
underline="always"
target="_blank"
rel="noopener noreferrer"
href="https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases"
@ -591,19 +704,71 @@ const Bridge = ({ chainId, address, config, connect }) => {
? (
<Box width="100%" display="flex" flexDirection="column" gap="0px">
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Est. Commission:</Typography>}
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Estimated Fee:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{incomingFee.toFixed(4)}%</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Finality Delay:</Typography>}
{!isVerySmallScreen && <Box display="flex" flexDirection="row">
<Typography fontSize="12px" lineHeight="15px">Finality Delay:</Typography>
<InfoTooltip message="This period ensures that the transaction is securely added to the ledger and cannot be altered or canceled." />
</Box>}
<Typography fontSize="12px" lineHeight="15px">{finalityDelay} blocks</Typography>
</Box>
<hr width="100%" />
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Current Epoch:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{currentSession ?? 0}</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Number of validators:</Typography>}
{!isVerySmallScreen && <Box display="flex" flexDirection="row">
<Typography fontSize="12px" lineHeight="15px">Number of validators:</Typography>
<InfoTooltip message={<>
<Typography variant="subtitle1">Validators</Typography>
<Box
width="280px"
height="250px"
margin="0"
padding="0"
marginTop="5px"
display="flex"
flexDirection="column"
sx={{ overflowY: "auto" }}
>
{authorities?.map((authority, idx) => {
const authorityAddress = ss58Address(authority.asHex(), 1996);
const clapInfo = clapsInSession?.at(idx)?.at(1);
return (
<Box
key={idx}
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
sx={{
fontSize: "10px",
color: !clapInfo?.disabled
? theme.colors.primary[300]
: theme.colors.feedback.error
}}
>
<div
style={{
width: "250px",
overflow: 'hidden',
whiteSpace: 'normal',
textOverflow: "ellipsis"
}}
>
{authorityAddress}
</div>
<div>{clapInfo?.claps ?? 0}</div>
</Box>
)
})}
</Box>
</>} />
</Box>}
<Typography fontSize="12px" lineHeight="15px">{clapsInSessionLength}</Typography>
</Box>
</Box>
@ -625,11 +790,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
loading={isPending}
onClick={() => ghostOrConnect()}
>
{address === "" ?
"Connect"
:
"Bridge"
}
{address === "" ? "Connect" : "Bridge" }
</PrimaryButton>
</>
)}