on master help sign in
feat: implement progress button for ethereum transactions
commit54fae1c0d240641f0a9d3ae8f42d6356079bc09c authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateTue 20 Aug 2019, 18:13 (7 years ago) parent0ab8d9d
9 files changed+104−19
src/components/CopiableAddress.js+23 src/components/InlineEthereumTransaction.js+14−3 src/components/LoadableButton.js−7 src/components/LoadingBar.js+2−2 src/components/ProgressButton.js+32 src/indigo-react/components/Button.js+6−4 src/lib/useEthereumTransaction.js+19−1 src/style/bridge.scss+6 src/views/Invite/InviteEmail.js+2−2
src/components/CopiableAddress.jsnew file+23
@@ -0,0 +1,23 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+import useCopiable from 'lib/useCopiable';
5+
6+export default function CopiableAddress({
7+ as: As = 'span',
8+ children,
9+ className,
10+}) {
11+ const [doCopy, didCopy] = useCopiable(children);
12+
13+ return (
14+ <As
15+ onClick={doCopy}
16+ className={cn(className, 'mono', {
17+ pointer: !didCopy,
18+ green3: didCopy,
19+ })}>
20+ {children}
21+ </As>
22+ );
23+}
src/components/InlineEthereumTransaction.js+14−3
@@ -21,6 +21,7 @@ import Condition from 'form/Condition';
2121import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
2222import WarningBox from './WarningBox';
2323import CopyButton from './CopyButton';
24+import ProgressButton from './ProgressButton';
2425
2526export default function InlineEthereumTransaction({
2627 // from useEthereumTransaction.bind
@@ -42,6 +43,7 @@ export default function InlineEthereumTransaction({
4243 chainId,
4344 needFunds,
4445 signedTransaction,
46+ confirmationProgress,
4547
4648 // additional from parent
4749 label = 'Generate & Sign Transaction',
@@ -53,9 +55,9 @@ export default function InlineEthereumTransaction({
5355 // show configure controls pre-broadcast
5456 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
5557 // show the send/loading button while signed, broadcasting, or confirme
56− const showBroadcastButton = signed || broadcasted || confirmed;
58+ const showBroadcastButton = signed;
59+ const showLoadingButton = broadcasted || confirmed;
5760 const canBroadcast = signed && !needFunds;
58− const isLoading = broadcasted || confirmed;
5961 // show signed tx only when signing (for offline usage)
6062 const showSignedTx = signed;
6163
@@ -100,11 +102,20 @@ export default function InlineEthereumTransaction({
100102 solid
101103 success
102104 disabled={!canBroadcast}
103− loading={isLoading}
104105 onClick={() => broadcast()}>
105106 Send Transaction
106107 </Grid.Item>
107108 );
109+ } else if (showLoadingButton) {
110+ return (
111+ <Grid.Item
112+ full
113+ as={ProgressButton}
114+ disabled={true}
115+ progress={confirmationProgress}>
116+ Sending Transaction
117+ </Grid.Item>
118+ );
108119 } else {
109120 return (
110121 <Grid.Item
src/components/LoadableButton.jsdeleted−7
@@ -1,7 +0,0 @@
1−import React from 'react';
2−import { Button } from 'indigo-react';
3−
4−// TODO: implement loading bar behind text
5−export default function LoadableButton({ ...rest }) {
6− return <Button {...rest}></Button>;
7−}
src/components/LoadingBar.js+2−2
@@ -2,11 +2,11 @@ import React from 'react';
22import cn from 'classnames';
33
44// progrss is [0, 1]
5−export default function LoadingBar({ className, progress = 1 }) {
5+export default function LoadingBar({ className, progress = 1.0 }) {
66 return (
77 <div className={cn('rel bg-gray2', className)} style={{ height: '4px' }}>
88 <div
9− className="abs bg-green3"
9+ className="abs bg-green3 animated-width"
1010 style={{
1111 top: 0,
1212 bottom: 0,
src/components/ProgressButton.jsnew file+32
@@ -0,0 +1,32 @@
1+import React from 'react';
2+import cn from 'classnames';
3+import { Button } from 'indigo-react';
4+import { blinkIf } from './Blinky';
5+
6+export default function ProgressButton({
7+ as: As = Button,
8+ progress = 0.0,
9+ color = 'green4',
10+ success = true,
11+ ...rest
12+}) {
13+ return (
14+ <As
15+ {...rest}
16+ solid
17+ success={success}
18+ accessory={blinkIf(progress < 1.0, '->')}
19+ background={
20+ <span
21+ className={cn('abs z1 animated-width', `bg-${color}`)}
22+ style={{
23+ left: 0,
24+ top: 0,
25+ right: 0,
26+ bottom: 0,
27+ width: `${progress * 100.0}%`,
28+ }}></span>
29+ }
30+ />
31+ );
32+}
src/indigo-react/components/Button.js+6−4
@@ -15,6 +15,7 @@ export default function Button({
1515 className,
1616 accessory = '→',
1717 onClick,
18+ background,
1819 children,
1920 ...rest
2021}) {
@@ -23,7 +24,7 @@ export default function Button({
2324 as={As}
2425 gap={1}
2526 className={cn(
26− 'pointer pv4 truncate flex-row justify-between us-none',
27+ 'rel pointer pv4 truncate flex-row justify-between us-none',
2728 {
2829 p4: solid,
2930 },
@@ -49,17 +50,18 @@ export default function Button({
4950 }}
5051 onClick={!disabled && onClick ? onClick : undefined}
5152 {...rest}>
52− <Grid.Item full as={Flex} justify="between">
53+ {background}
54+ <Grid.Item full as={Flex} justify="between" className="z2">
5355 <span>{children}</span>
5456 <div className={cn('pl4')}>{accessory}</div>
5557 </Grid.Item>
5658 {detail && (
57− <Grid.Item full as={HelpText}>
59+ <Grid.Item full as={HelpText} className="z2">
5860 {detail}
5961 </Grid.Item>
6062 )}
6163 {disabled && disabledDetail && (
62− <Grid.Item full className="f6 black mt1">
64+ <Grid.Item full className="f6 black mt1 z2">
6365 {disabledDetail}
6466 </Grid.Item>
6567 )}
src/lib/useEthereumTransaction.js+19−1
@@ -17,6 +17,7 @@ import * as need from 'lib/need';
1717import { ensureFundsFor } from 'lib/tank';
1818import useDeepEqualReference from './useDeepEqualReference';
1919import useGasPrice from './useGasPrice';
20+import timeout from './timeout';
2021
2122const STATE = {
2223 NONE: 'NONE',
@@ -65,6 +66,7 @@ export default function useEthereumTransaction(
6566 const [signedTransaction, setSignedTransaction] = useState();
6667 const [txHash, setTxHash] = useState();
6768 const [needFunds, setNeedFunds] = useState();
69+ const [confirmationProgress, setConfirmationProgress] = useState(0.0);
6870
6971 const initializing = nonce === undefined || chainId === undefined;
7072 const constructed = !!unsignedTransaction;
@@ -126,6 +128,7 @@ export default function useEthereumTransaction(
126128
127129 const broadcast = useCallback(async () => {
128130 try {
131+ setConfirmationProgress(0.0);
129132 setError(undefined);
130133
131134 const rawTx = hexify(signedTransaction.serialize());
@@ -156,8 +159,14 @@ export default function useEthereumTransaction(
156159 setState(STATE.BROADCASTED);
157160 setTxHash(txHash);
158161
162+ await timeout(500); // wait for .animated-width to complete
163+
164+ setConfirmationProgress(0.2);
165+
159166 await waitForTransactionConfirm(_web3, txHash);
160167
168+ setConfirmationProgress(0.9);
169+
161170 setState(STATE.CONFIRMED);
162171 } catch (error) {
163172 setError(error);
@@ -181,6 +190,7 @@ export default function useEthereumTransaction(
181190 setState(STATE.NONE);
182191 setError(undefined);
183192 setNeedFunds(undefined);
193+ setConfirmationProgress(0.0);
184194 }, [resetGasPrice, setError]);
185195
186196 useEffect(() => {
@@ -236,6 +246,14 @@ export default function useEthereumTransaction(
236246 return;
237247 }
238248
249+ setConfirmationProgress(1.0);
250+
251+ await timeout(500); // wait for .animated-width to complete
252+
253+ if (!mounted) {
254+ return;
255+ }
256+
239257 setState(STATE.COMPLETED);
240258 })();
241259 }
@@ -259,7 +277,7 @@ export default function useEthereumTransaction(
259277 reset,
260278 error,
261279 inputsLocked,
262− //
280+ confirmationProgress,
263281 txHash,
264282 signedTransaction,
265283 gasPrice,
src/style/bridge.scss+6
@@ -66,3 +66,9 @@ input[type='range'] {
6666 background-color: #e6e6e6;
6767 }
6868}
69+
70+// animated width
71+
72+.animated-width {
73+ transition: width 500ms ease-out;
74+}
src/views/Invite/InviteEmail.js+2−2
@@ -41,7 +41,7 @@ import useSetState from 'lib/useSetState';
4141import pluralize from 'lib/pluralize';
4242import useMailer from 'lib/useMailer';
4343
44−import LoadableButton from 'components/LoadableButton';
44+import ProgressButton from 'components/ProgressButton';
4545import Highlighted from 'components/Highlighted';
4646import BridgeForm from 'form/BridgeForm';
4747import { Field } from 'react-final-form';
@@ -518,7 +518,7 @@ export default function InviteEmail() {
518518 ) : (
519519 <Grid.Item
520520 full
521− as={LoadableButton}
521+ as={ProgressButton}
522522 className="mt4"
523523 disabled={!canSend}
524524 accessory={`${visualProgress}/${fields.length}`}