Skip to content
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"prepare": "husky install"
},
"dependencies": {
"@bitcoin-design/bitcoin-icons-react": "^0.1.10",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why new library for just one icon. we use popicons by default. and i guess copy icon should be there as well PopiconsCopyLine

"@bitcoinerlab/secp256k1": "^1.1.1",
"@getalby/sdk": "^3.9.0",
"@headlessui/react": "^1.7.18",
Expand Down
56 changes: 47 additions & 9 deletions src/app/components/QRCode/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import ReactQRCode from "react-qr-code";
import { classNames, useTheme } from "~/app/utils";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { CopyIcon } from "@bitcoin-design/bitcoin-icons-react/filled";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use popicons


export type Props = {
value: string;
Expand All @@ -15,21 +18,56 @@ export type Props = {
// (meaning you have to aim your phone very precisely and have to wait longer for the reader
// to recognize the QR code)
level?: "Q" | undefined;
onCopy?: () => void;
};

export default function QRCode({ value, size, level, className }: Props) {
export default function QRCode({
value,
size,
level,
className,
onCopy,
}: Props) {
const theme = useTheme();
const fgColor = theme === "dark" ? "#FFFFFF" : "#000000";
const bgColor = theme === "dark" ? "#000000" : "#FFFFFF";
const { t } = useTranslation("components");
const [isHovering, setIsHovering] = useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(value);
onCopy?.();
};
Comment on lines +29 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for clipboard API.

navigator.clipboard.writeText() returns a Promise that can reject (e.g., clipboard permission denied, insecure context). The current implementation ignores errors, which could result in silent failures with no user feedback.

🛡️ Proposed fix
  const handleCopy = () => {
-   navigator.clipboard.writeText(value);
-   toast.success(t("actions.copied_to_clipboard"));
+   navigator.clipboard.writeText(value)
+     .then(() => {
+       toast.success(t("actions.copied_to_clipboard"));
+     })
+     .catch((err) => {
+       console.error("Failed to copy:", err);
+       toast.error(t("actions.copy_failed"));
+     });
  };

Alternatively, using async/await:

- const handleCopy = () => {
-   navigator.clipboard.writeText(value);
-   toast.success(t("actions.copied_to_clipboard"));
+ const handleCopy = async () => {
+   try {
+     await navigator.clipboard.writeText(value);
+     toast.success(t("actions.copied_to_clipboard"));
+   } catch (err) {
+     if (err instanceof Error) {
+       toast.error(err.message);
+     }
+   }
  };

Note: You may need to add a translation key for the error case (e.g., actions.copy_failed).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/components/QRCode/index.tsx` around lines 29 - 32, The handleCopy
function currently calls navigator.clipboard.writeText(value) without awaiting
or handling rejection; change it to an async flow (or attach .then/.catch)
around navigator.clipboard.writeText in src/app/components/QRCode/index.tsx so
failures are caught and handled, and on success still call
toast.success(t("actions.copied_to_clipboard")), while on error call
toast.error(t("actions.copy_failed")) (or another translation key) and
optionally log the error; update the handleCopy implementation and ensure you
reference navigator.clipboard.writeText, handleCopy, toast.success/toast.error,
and t(...) in the fix.


return (
<ReactQRCode
value={value}
size={size}
fgColor={fgColor}
bgColor={bgColor}
className={classNames("w-full h-auto rounded-md", className ?? "")}
level={level}
/>
<div
className="relative cursor-pointer"
onClick={handleCopy}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
>
<ReactQRCode
value={value}
size={size}
fgColor={fgColor}
bgColor={bgColor}
className={classNames(
"w-full h-auto rounded-md transition-opacity",
isHovering ? "opacity-80" : "opacity-100",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for hover why we need to use state varbiles? can use tailwind here only?

eg.
className="text-gray-600 hover: text-gray-800"

className ?? ""
)}
level={level}
/>
{isHovering && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 rounded-md">
<div className="flex flex-col items-center">
<CopyIcon className="h-8 w-8 text-white" />
<span className="text-white font-medium mt-2">
{t("allowance_menu.qrcode.click_to_copy")}
</span>
</div>
</div>
)}
</div>
);
}
3 changes: 3 additions & 0 deletions src/app/screens/Accounts/Detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ function AccountDetail() {
<QRCode
value={`lndhub://${lndHubData.login}:${lndHubData.password}@${lndHubData.url}/`}
size={256}
onCopy={() =>
toast.success("Copied to clipboard")
}
/>
</div>
<div className="w-full">
Expand Down
1 change: 1 addition & 0 deletions src/app/screens/Receive/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function Receive() {
value={lightningAddress}
size={192}
level="Q"
onCopy={() => toast.success("Copied to clipboard")}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this new callback was introduced? just to show success message? can't we do same within the qr code component?

/>
</>
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/screens/ReceiveInvoice/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ function ReceiveInvoice() {
<QRCode
value={invoice.paymentRequest.toUpperCase()}
size={512}
onCopy={() => toast.success("Copied to clipboard!")}
/>
{isAlbyOAuthUser ? (
<>
Expand Down
5 changes: 4 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,10 @@
"always_allow": "Always allow",
"always_reject": "Always reject"
},
"website_permissions": "Website Permissions"
"website_permissions": "Website Permissions",
"qrcode": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have copy_clipboard and copied_to_clipboard in common section of translations. always check before introducing new ones

"click_to_copy": "Click to copy"
}
},
"qrcode_scanner": {
"title": "Scan QR Code",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==

"@bitcoin-design/bitcoin-icons-react@^0.1.10":
version "0.1.10"
resolved "https://registry.yarnpkg.com/@bitcoin-design/bitcoin-icons-react/-/bitcoin-icons-react-0.1.10.tgz#11acf1d386c1094d3eff673ca236345860762f64"
integrity sha512-f7GSutKHa4EK4LWI/phnGCJsN8fzFbVAVQ4F1MYxiza34LVmXmbgHUmdP/BR8ZeQSIbZLt19inpJZDBtQvYe4Q==

"@bitcoinerlab/secp256k1@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.1.1.tgz#938305c30505b67b15f3de53627bc9d8b4cc7680"
Expand Down