Merge pull request #125 from cosmos/feat/better-share-link

Show share link
This commit is contained in:
Simon Warta
2023-05-31 11:44:57 +02:00
committed by GitHub
12 changed files with 161 additions and 141 deletions
@@ -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 -1
View File
@@ -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 {
+61 -41
View File
@@ -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;
+14 -10
View File
@@ -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>{`
+37 -42
View File
@@ -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;