Skip to content

Commit 7e30ce3

Browse files
committed
frontend: tap/click to copy QR code
to improve UX especially on mobile, we're enabling tap/click to copy for our QRCode component.
1 parent b02e97f commit 7e30ce3

File tree

5 files changed

+107
-7
lines changed

5 files changed

+107
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,55 @@
1+
2+
.checkContainer {
3+
align-items: center;
4+
background: var(--color-success);
5+
border-radius: 100%;
6+
display: flex;
7+
height: 100%;
8+
justify-content: center;
9+
position: absolute;
10+
top: 0;
11+
width: 100%;
12+
}
13+
14+
.copiedText {
15+
margin-top: 16px;
16+
transition: opacity 250ms;
17+
}
18+
119
.empty {
220
width: 256px;
321
height: 256px;
422
color: var(--color-secondary);
23+
}
24+
25+
.hide {
26+
opacity: 0;
27+
}
28+
29+
.hiddenInput {
30+
height: 0;
31+
opacity: 0;
32+
}
33+
34+
.outerContainer {
35+
display: flex;
36+
margin: auto;
37+
position: relative;
38+
}
39+
40+
.qrCodeContainer {
41+
position: absolute;
42+
width: 100%;
43+
height: 100%;
44+
z-index: 1;
45+
transition: opacity 250ms;
46+
}
47+
48+
49+
.qrCodeContainer:hover {
50+
cursor: pointer;
51+
}
52+
53+
.show {
54+
opacity: 1;
555
}

frontends/web/src/components/qrcode/qrcode.tsx

+50-1
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,24 @@
1717

1818
import { useLoad } from '../../hooks/api';
1919
import { getQRCode } from '../../api/backend';
20+
import { Check } from '../icon';
21+
import { useEffect, useRef, useState } from 'react';
22+
import { useTranslation } from 'react-i18next';
2023
import style from './qrcode.module.css';
2124

2225
type TProps = {
2326
data?: string;
2427
size?: number;
28+
tapToCopy?: boolean;
2529
};
2630

2731
export const QRCode = ({
2832
data,
2933
size = 256,
34+
tapToCopy = true
3035
}: TProps) => {
3136
const qrCode = useLoad(data !== undefined ? getQRCode(data) : null, [data]);
37+
3238
if (!qrCode) {
3339
if (data !== undefined) {
3440
return <div className={style.empty}></div>;
@@ -45,6 +51,49 @@ export const QRCode = ({
4551
}
4652

4753
return (
48-
<img width={size} height={size} src={qrCode.data} />
54+
tapToCopy ?
55+
<TapToCopyQRCode data={data} qrCodeData={qrCode.data} size={size} /> :
56+
<img width={size} height={size} src={qrCode.data} />
57+
);
58+
};
59+
60+
type TTapToCopyQRCodeProps = {
61+
data?: string;
62+
qrCodeData: string
63+
size: number
64+
}
65+
66+
const TapToCopyQRCode = ({ data, qrCodeData, size }: TTapToCopyQRCodeProps) => {
67+
const inputRef = useRef<HTMLInputElement>(null);
68+
const [success, setSuccess] = useState(false);
69+
70+
const { t } = useTranslation();
71+
72+
useEffect(() => {
73+
if (success) {
74+
setTimeout(() => setSuccess(false), 1500);
75+
}
76+
}, [success]);
77+
78+
79+
const handleCopy = () => {
80+
inputRef.current?.select();
81+
if (document.execCommand('copy')) {
82+
setSuccess(true);
83+
}
84+
};
85+
86+
87+
return (
88+
<div onClick={handleCopy}>
89+
<input className={style.hiddenInput} ref={inputRef} value={data} readOnly/>
90+
<div style={{ width: size, height: size }} className={style.outerContainer}>
91+
<img className={`${style.qrCodeContainer} ${success ? style.hide : style.show}`} width={size} height={size} src={qrCodeData} />
92+
<div className={`${style.checkContainer} ${style.show}`}>
93+
<Check width={size / 2} height={size / 2} />
94+
</div>
95+
</div>
96+
<p className={`${style.copiedText} ${success ? style.show : style.hide}`}>{t('receive.qrCodeCopiedMessage')}</p>
97+
</div>
4998
);
5099
};

frontends/web/src/locales/en/app.json

+1
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,7 @@
13801380
"description": "To receive other tokens, enable them in the settings. If you deposit other tokens, they might not be accessible.",
13811381
"warning": "Make sure to only receive {{coinName}} on this address."
13821382
},
1383+
"qrCodeCopiedMessage": "Copied!",
13831384
"scriptType": {
13841385
"p2tr": "Taproot (newest format)",
13851386
"p2wpkh": "Native Segwit (default)",

frontends/web/src/routes/account/receive/receive-bb01.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const Receive = ({
164164
{ currentAddresses && (
165165
<div style={{ position: 'relative' }}>
166166
<div className={style.qrCodeContainer}>
167-
<QRCode data={enableCopy ? uriPrefix + address : undefined} />
167+
<QRCode tapToCopy={false} data={enableCopy ? uriPrefix + address : undefined} />
168168
</div>
169169
<div className={style.labels}>
170170
{ currentAddresses.length > 1 && (
@@ -266,7 +266,7 @@ export const Receive = ({
266266
{t('receive.onlyThisCoin.description')}
267267
</p>
268268
)}
269-
<QRCode data={uriPrefix + address} />
269+
<QRCode tapToCopy={false} data={uriPrefix + address} />
270270
<p>{t('receive.verifyInstruction')}</p>
271271
</>}
272272
</div>

frontends/web/src/routes/device/bitbox01/settings/components/mobile-pairing.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ class MobilePairing extends Component<Props, State> {
172172
<div className="column column-1-2">
173173
<label className="text-center">Apple App Store</label>
174174
<div className="flex flex-column flex-center flex-items-center">
175-
<QRCode data="https://itunes.apple.com/us/app/digital-bitbox-2fa/id1079896740" size={148} />
175+
<QRCode tapToCopy={false} data="https://itunes.apple.com/us/app/digital-bitbox-2fa/id1079896740" size={148} />
176176
<a target="_blank" rel="noreferrer" href="https://itunes.apple.com/us/app/digital-bitbox-2fa/id1079896740"><img src={appStoreBadge} className={style.badge} /></a>
177177
</div>
178178
</div>
179179
<div className="column column-1-2">
180180
<label className="text-center">Google Play Store</label>
181181
<div className="flex flex-column flex-center flex-items-center">
182-
<QRCode data="https://play.google.com/store/apps/details?id=com.digitalbitbox.tfa" size={148} />
182+
<QRCode tapToCopy={false} data="https://play.google.com/store/apps/details?id=com.digitalbitbox.tfa" size={148} />
183183
<a target="_blank" rel="noreferrer" href="https://play.google.com/store/apps/details?id=com.digitalbitbox.tfa"><img src={playStoreBadge} className={style.badge} /></a>
184184
</div>
185185
</div>
@@ -193,14 +193,14 @@ class MobilePairing extends Component<Props, State> {
193193
<div>
194194
<p className="m-top-none"><strong className="m-right-quarter">2.</strong>{t('pairing.start.step2')}</p>
195195
<div className="text-center">
196-
<QRCode data={JSON.stringify(channel)} size={196} />
196+
<QRCode tapToCopy={false} data={JSON.stringify(channel)} size={196} />
197197
</div>
198198
</div>
199199
</div>
200200
</div>
201201
);
202202
} else if (status === 'connectOnly') {
203-
content = (<QRCode data={JSON.stringify({ channel, connectOnly: true })} />);
203+
content = (<QRCode tapToCopy={false} data={JSON.stringify({ channel, connectOnly: true })} />);
204204
} else {
205205
content = (<p className="m-top-none">{t(`pairing.${status}.text`)}</p>);
206206
}

0 commit comments

Comments
 (0)