Merge pull request #125 from cosmos/feat/better-share-link
Show share link
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { explorerLinkTx } from "../../lib/displayHelpers";
|
||||
import Button from "../inputs/Button";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import HashView from "./HashView";
|
||||
import Button from "../inputs/Button";
|
||||
import { explorerLinkTx } from "../../lib/displayHelpers";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
|
||||
interface Props {
|
||||
transactionHash: string;
|
||||
@@ -21,7 +19,7 @@ const CompletedTransaction = ({ transactionHash }: Props) => {
|
||||
<svg viewBox="0 0 77 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 30L26 51L72 5" stroke="white" strokeWidth="12" />
|
||||
</svg>
|
||||
<p>This transaction has been broadcast.</p>
|
||||
<p>This transaction has been broadcast</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
interface Props {
|
||||
@@ -26,6 +25,7 @@ const CopyAndPaste = (props: Props) => (
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.icon:hover path {
|
||||
|
||||
@@ -1,52 +1,72 @@
|
||||
import React from "react";
|
||||
import { MultisigThresholdPubkey } from "@cosmjs/amino";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { DbSignature } from "../../types";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import CopyAndPaste from "./CopyAndPaste";
|
||||
|
||||
interface Props {
|
||||
signatures: DbSignature[];
|
||||
pubkey: MultisigThresholdPubkey;
|
||||
}
|
||||
|
||||
const ThresholdInfo = ({ signatures, pubkey }: Props) => (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
<h2>Signatures</h2>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<p>
|
||||
Once the number of required signatures have been created, this transaction will be ready to
|
||||
broadcast.
|
||||
</p>
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<div className="threshold">
|
||||
<div className="current">{signatures.length}</div>
|
||||
<div className="label divider">of</div>
|
||||
<div className="required">{pubkey.value.threshold}</div>
|
||||
<div className="label">signatures complete</div>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
<style jsx>{`
|
||||
.threshold {
|
||||
display: flex;
|
||||
font-size: 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.threshold div {
|
||||
padding: 0 5px;
|
||||
}
|
||||
.label {
|
||||
font-size: 16px;
|
||||
}
|
||||
p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
`}</style>
|
||||
</StackableContainer>
|
||||
);
|
||||
const ThresholdInfo = ({ signatures, pubkey }: Props) => {
|
||||
const [urlToCopy, setUrlToCopy] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const urlWithoutQuery = window.location.href.split("?")[0];
|
||||
setUrlToCopy(urlWithoutQuery);
|
||||
}, []);
|
||||
|
||||
const remainingSignatures = Number(pubkey.value.threshold) - signatures.length;
|
||||
if (!remainingSignatures) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<div className="threshold">
|
||||
<div className="current">{signatures.length}</div>
|
||||
<div className="label divider">of</div>
|
||||
<div className="required">{pubkey.value.threshold}</div>
|
||||
<div className="label">signatures complete</div>
|
||||
</div>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<p>
|
||||
{remainingSignatures} remaining {remainingSignatures > 1 ? "signatures" : "signature"}{" "}
|
||||
before this transaction can be broadcast
|
||||
</p>
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<CopyAndPaste copyText={urlToCopy} />
|
||||
<p style={{ margin: 0 }}>
|
||||
Copy the current URL and share it with the other members in the multisig so they can
|
||||
sign it
|
||||
</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
</StackableContainer>
|
||||
<style jsx>{`
|
||||
.threshold {
|
||||
display: flex;
|
||||
font-size: 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.threshold div {
|
||||
padding: 0 5px;
|
||||
}
|
||||
.label {
|
||||
font-size: 16px;
|
||||
}
|
||||
p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThresholdInfo;
|
||||
|
||||
@@ -8,7 +8,7 @@ interface TxMsgClaimRewardsDetailsProps {
|
||||
const TxMsgClaimRewardsDetails = ({ msg }: TxMsgClaimRewardsDetailsProps) => (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgWithdrawDelegatorReward</h2>
|
||||
<h3>MsgWithdrawDelegatorReward</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Validator Address:</label>
|
||||
@@ -17,7 +17,7 @@ const TxMsgClaimRewardsDetails = ({ msg }: TxMsgClaimRewardsDetailsProps) => (
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -13,7 +13,7 @@ const TxMsgDelegateDetails = ({ msg }: TxMsgDelegateDetailsProps) => {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgDelegate</h2>
|
||||
<h3>MsgDelegate</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
@@ -26,7 +26,7 @@ const TxMsgDelegateDetails = ({ msg }: TxMsgDelegateDetailsProps) => {
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -13,7 +13,7 @@ const TxMsgRedelegateDetails = ({ msg }: TxMsgRedelegateDetailsProps) => {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgBeginRedelegate</h2>
|
||||
<h3>MsgBeginRedelegate</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
@@ -32,7 +32,7 @@ const TxMsgRedelegateDetails = ({ msg }: TxMsgRedelegateDetailsProps) => {
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -13,7 +13,7 @@ const TxMsgSendDetails = ({ msg }: TxMsgSendDetailsProps) => {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgSend</h2>
|
||||
<h3>MsgSend</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
@@ -26,7 +26,7 @@ const TxMsgSendDetails = ({ msg }: TxMsgSendDetailsProps) => {
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -8,7 +8,7 @@ interface TxMsgSetWithdrawAddressDetailsProps {
|
||||
const TxMsgSetWithdrawAddressDetails = ({ msg }: TxMsgSetWithdrawAddressDetailsProps) => (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgSetWithdrawAddress</h2>
|
||||
<h3>MsgSetWithdrawAddress</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Withdraw Address:</label>
|
||||
@@ -17,7 +17,7 @@ const TxMsgSetWithdrawAddressDetails = ({ msg }: TxMsgSetWithdrawAddressDetailsP
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -13,7 +13,7 @@ const TxMsgUndelegateDetails = ({ msg }: TxMsgUndelegateDetailsProps) => {
|
||||
return (
|
||||
<>
|
||||
<li>
|
||||
<h2>MsgUndelegate</h2>
|
||||
<h3>MsgUndelegate</h3>
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
@@ -26,7 +26,7 @@ const TxMsgUndelegateDetails = ({ msg }: TxMsgUndelegateDetailsProps) => {
|
||||
</div>
|
||||
</li>
|
||||
<style jsx>{`
|
||||
li:not(:has(h2)) {
|
||||
li:not(:has(h3)) {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -28,16 +28,6 @@ const TransactionInfo = ({ tx }: Props) => {
|
||||
<>
|
||||
<ul className="meta-data">
|
||||
<>
|
||||
{tx.msgs.map((msg, index) => (
|
||||
<StackableContainer key={index} lessPadding lessMargin>
|
||||
{isTxMsgSend(msg) ? <TxMsgSendDetails msg={msg} /> : null}
|
||||
{isTxMsgDelegate(msg) ? <TxMsgDelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgUndelegate(msg) ? <TxMsgUndelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgRedelegate(msg) ? <TxMsgRedelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgClaimRewards(msg) ? <TxMsgClaimRewardsDetails msg={msg} /> : null}
|
||||
{isTxMsgSetWithdrawAddress(msg) ? <TxMsgSetWithdrawAddressDetails msg={msg} /> : null}
|
||||
</StackableContainer>
|
||||
))}
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
{tx.fee ? (
|
||||
<>
|
||||
@@ -58,6 +48,20 @@ const TransactionInfo = ({ tx }: Props) => {
|
||||
</li>
|
||||
) : null}
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
{tx.msgs.map((msg, index) => (
|
||||
<StackableContainer key={index} lessPadding lessMargin>
|
||||
{isTxMsgSend(msg) ? <TxMsgSendDetails msg={msg} /> : null}
|
||||
{isTxMsgDelegate(msg) ? <TxMsgDelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgUndelegate(msg) ? <TxMsgUndelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgRedelegate(msg) ? <TxMsgRedelegateDetails msg={msg} /> : null}
|
||||
{isTxMsgClaimRewards(msg) ? <TxMsgClaimRewardsDetails msg={msg} /> : null}
|
||||
{isTxMsgSetWithdrawAddress(msg) ? (
|
||||
<TxMsgSetWithdrawAddressDetails msg={msg} />
|
||||
) : null}
|
||||
</StackableContainer>
|
||||
))}
|
||||
</StackableContainer>
|
||||
</>
|
||||
</ul>
|
||||
<style jsx>{`
|
||||
|
||||
@@ -193,33 +193,37 @@ const TransactionSigning = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
{signing === "signed" ? (
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<h2>Current Signers</h2>
|
||||
{props.signatures.map((signature, i) => (
|
||||
<StackableContainer lessPadding lessRadius lessMargin key={`${signature.address}_${i}`}>
|
||||
<HashView hash={signature.address} />
|
||||
</StackableContainer>
|
||||
))}
|
||||
{!props.signatures.length ? <p>No signatures yet</p> : null}
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
{signing === "signed" ? (
|
||||
<div className="confirmation">
|
||||
<svg viewBox="0 0 77 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 30L26 51L72 5" stroke="white" strokeWidth="12" />
|
||||
</svg>
|
||||
<p>You've signed this transaction.</p>
|
||||
<p>You've signed this transaction</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
) : null}
|
||||
{signing === "not_a_member" ? (
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
) : null}
|
||||
{signing === "not_a_member" ? (
|
||||
<div className="multisig-error">
|
||||
<p>You don't belong to this multisig.</p>
|
||||
<p>You don't belong to this multisig</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
) : null}
|
||||
{signing === "not_signed" ? (
|
||||
<>
|
||||
<h2>Sign this transaction</h2>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
) : null}
|
||||
{signing === "not_signed" ? (
|
||||
<>
|
||||
{walletAccount ? (
|
||||
<>
|
||||
<p>
|
||||
Connected signer {walletAccount.bech32Address} (
|
||||
{walletType ?? "Unknown wallet type"}).
|
||||
You can sign this transaction with {walletAccount.bech32Address} (
|
||||
{walletType ?? "Unknown wallet type"})
|
||||
</p>
|
||||
<Button
|
||||
label="Sign transaction"
|
||||
@@ -229,6 +233,7 @@ const TransactionSigning = (props: Props) => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2>Choose wallet to sign</h2>
|
||||
<Button label="Connect Keplr" onClick={connectKeplr} loading={loading.keplr} />
|
||||
<Button
|
||||
label="Connect Ledger (WebUSB)"
|
||||
@@ -237,32 +242,22 @@ const TransactionSigning = (props: Props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{sigError ? (
|
||||
<StackableContainer lessPadding lessRadius lessMargin>
|
||||
<div className="signature-error">
|
||||
<p>This account has already signed this transaction</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
</>
|
||||
) : null}
|
||||
{sigError && (
|
||||
<StackableContainer lessPadding lessRadius lessMargin>
|
||||
<div className="signature-error">
|
||||
<p>This account has already signed this transaction.</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
)}
|
||||
{connectError && (
|
||||
<StackableContainer lessPadding lessRadius lessMargin>
|
||||
<div className="signature-error">
|
||||
<p>{connectError}</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
)}
|
||||
<h2>Current Signers</h2>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
{props.signatures.map((signature, i) => (
|
||||
<StackableContainer lessPadding lessRadius lessMargin key={`${signature.address}_${i}`}>
|
||||
<HashView hash={signature.address} />
|
||||
) : null}
|
||||
{connectError ? (
|
||||
<StackableContainer lessPadding lessRadius lessMargin>
|
||||
<div className="signature-error">
|
||||
<p>{connectError}</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
))}
|
||||
|
||||
{props.signatures.length === 0 && <p>No signatures yet</p>}
|
||||
) : null}
|
||||
</StackableContainer>
|
||||
<style jsx>{`
|
||||
p {
|
||||
@@ -302,7 +297,7 @@ const TransactionSigning = (props: Props) => {
|
||||
font-size: 16px;
|
||||
}
|
||||
`}</style>
|
||||
</StackableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -141,48 +141,51 @@ const TransactionPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
const isThresholdMet = pubkey
|
||||
? currentSignatures.length >= Number(pubkey.value.threshold)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<Page rootMultisig={multisigAddress}>
|
||||
<StackableContainer base>
|
||||
<StackableContainer>
|
||||
<h1>{transactionHash ? "Completed Transaction" : "In Progress Transaction"}</h1>
|
||||
</StackableContainer>
|
||||
{accountError && (
|
||||
{accountError ? (
|
||||
<StackableContainer>
|
||||
<div className="multisig-error">
|
||||
<p>Multisig address could not be found.</p>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
)}
|
||||
{transactionHash && <CompletedTransaction transactionHash={transactionHash} />}
|
||||
<TransactionInfo tx={txInfo} />
|
||||
{!transactionHash && pubkey && (
|
||||
<ThresholdInfo signatures={currentSignatures} pubkey={pubkey} />
|
||||
)}
|
||||
{pubkey &&
|
||||
currentSignatures.length >= parseInt(pubkey.value.threshold, 10) &&
|
||||
!transactionHash && (
|
||||
<>
|
||||
<Button
|
||||
label={isBroadcasting ? "Broadcasting..." : "Broadcast Transaction"}
|
||||
onClick={broadcastTx}
|
||||
primary
|
||||
disabled={isBroadcasting}
|
||||
) : null}
|
||||
{transactionHash ? <CompletedTransaction transactionHash={transactionHash} /> : null}
|
||||
{!transactionHash ? (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
{pubkey ? <ThresholdInfo signatures={currentSignatures} pubkey={pubkey} /> : null}
|
||||
{isThresholdMet ? (
|
||||
<>
|
||||
<Button
|
||||
label={isBroadcasting ? "Broadcasting..." : "Broadcast Transaction"}
|
||||
onClick={broadcastTx}
|
||||
primary
|
||||
disabled={isBroadcasting}
|
||||
/>
|
||||
{broadcastError ? <div className="broadcast-error">{broadcastError}</div> : null}
|
||||
</>
|
||||
) : null}
|
||||
{pubkey ? (
|
||||
<TransactionSigning
|
||||
tx={txInfo}
|
||||
transactionID={transactionID}
|
||||
pubkey={pubkey}
|
||||
signatures={currentSignatures}
|
||||
addSignature={addSignature}
|
||||
/>
|
||||
{broadcastError && <div className="broadcast-error">{broadcastError}</div>}
|
||||
</>
|
||||
)}
|
||||
{!transactionHash && !!pubkey && (
|
||||
<TransactionSigning
|
||||
tx={txInfo}
|
||||
transactionID={transactionID}
|
||||
pubkey={pubkey}
|
||||
signatures={currentSignatures}
|
||||
addSignature={addSignature}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</StackableContainer>
|
||||
) : null}
|
||||
<TransactionInfo tx={txInfo} />
|
||||
</StackableContainer>
|
||||
|
||||
<style jsx>{`
|
||||
.broadcast-error {
|
||||
background: firebrick;
|
||||
|
||||
Reference in New Issue
Block a user