From 0386638cbd5a62e747408b22be7db00fa8b1e62f Mon Sep 17 00:00:00 2001 From: MananTank Date: Sun, 27 Jul 2025 12:22:29 +0000 Subject: [PATCH] Dashboard: Migrate engine/overview from chakra to tailwind (#7723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on refactoring UI components and improving the user experience in the wallet management section of a dashboard application. It includes updates to modals, button components, and form handling while ensuring better accessibility and responsiveness. ### Detailed summary - Updated `create-backend-wallet-button.tsx` to modify button styles and add `tabIndex` for accessibility. - Enhanced tooltip functionality in `tooltip.tsx`. - Added `react-qrcode-logo` dependency for QR code generation. - Refactored `transaction-timeline.tsx` to improve transaction display and interaction. - Converted Chakra UI modals to custom dialog components for consistency. - Updated `transactions-table.tsx` to streamline transaction details display and manage state more effectively. - Refined backend wallet management in `backend-wallets-table.tsx`, replacing Chakra components with custom UI elements. - Improved form handling using `react-hook-form` and validation with `zod` in wallet-related modals. - Enhanced user feedback through toast notifications for actions like sending funds and deleting wallets. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/package.json | 3 +- .../dashboard/src/@/components/ui/tooltip.tsx | 2 +- .../components/backend-wallets-table.tsx | 716 ++++++++++-------- .../create-backend-wallet-button.tsx | 2 +- .../components/transaction-timeline.tsx | 302 ++++---- .../components/transactions-table.tsx | 492 ++++++------ pnpm-lock.yaml | 33 +- 7 files changed, 886 insertions(+), 664 deletions(-) diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 68c347994c1..7649442d419 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -56,7 +56,6 @@ "posthog-js": "1.256.1", "posthog-node": "^5.4.0", "prettier": "3.6.2", - "qrcode": "1.5.3", "react": "19.1.0", "react-children-utilities": "^2.10.0", "react-day-picker": "^8.10.1", @@ -65,6 +64,7 @@ "react-error-boundary": "6.0.0", "react-hook-form": "7.55.0", "react-markdown": "10.1.0", + "react-qrcode-logo": "^3.0.0", "react-table": "^7.8.0", "recharts": "2.15.3", "remark-gfm": "4.0.1", @@ -98,7 +98,6 @@ "@types/node": "22.14.1", "@types/papaparse": "^5.3.16", "@types/pluralize": "^0.0.33", - "@types/qrcode": "1.5.5", "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "@types/react-table": "^7.7.20", diff --git a/apps/dashboard/src/@/components/ui/tooltip.tsx b/apps/dashboard/src/@/components/ui/tooltip.tsx index 3a0ea11c6ae..687bfe4fac7 100644 --- a/apps/dashboard/src/@/components/ui/tooltip.tsx +++ b/apps/dashboard/src/@/components/ui/tooltip.tsx @@ -46,7 +46,7 @@ export function ToolTipLabel(props: { return ( - + {props.children} { + if (isAddress(val)) { + return true; + } + + return false; + }, + { + message: "Invalid wallet address", + }, + ), + amount: z.coerce.number().min(0, "Amount must be greater than 0"), +}); + +type EditWalletFormValues = z.infer; +type SendFundsFormValues = z.infer; + interface BackendWalletsTableProps { wallets: BackendWallet[]; instanceUrl: string; @@ -137,10 +173,10 @@ export const BackendWalletsTable: React.FC = ({ chainId, client, }) => { - const editDisclosure = useDisclosure(); - const receiveDisclosure = useDisclosure(); - const sendDisclosure = useDisclosure(); - const deleteDisclosure = useDisclosure(); + const [editOpen, setEditOpen] = useState(false); + const [receiveOpen, setReceiveOpen] = useState(false); + const [sendOpen, setSendOpen] = useState(false); + const [deleteOpen, setDeleteOpen] = useState(false); const queryClient = useQueryClient(); const columns = useMemo(() => { @@ -157,9 +193,7 @@ export const BackendWalletsTable: React.FC = ({ columnHelper.accessor("label", { cell: (cell) => { return ( - - {cell.getValue()} - +
{cell.getValue()}
); }, header: "Label", @@ -228,7 +262,7 @@ export const BackendWalletsTable: React.FC = ({ icon: , onClick: (wallet) => { setSelectedBackendWallet(wallet); - editDisclosure.onOpen(); + setEditOpen(true); }, text: "Edit", }, @@ -236,7 +270,7 @@ export const BackendWalletsTable: React.FC = ({ icon: , onClick: (wallet) => { setSelectedBackendWallet(wallet); - receiveDisclosure.onOpen(); + setReceiveOpen(true); }, text: "Receive funds", }, @@ -244,7 +278,7 @@ export const BackendWalletsTable: React.FC = ({ icon: , onClick: (wallet) => { setSelectedBackendWallet(wallet); - sendDisclosure.onOpen(); + setSendOpen(true); }, text: "Send funds", }, @@ -253,7 +287,7 @@ export const BackendWalletsTable: React.FC = ({ isDestructive: true, onClick: (wallet) => { setSelectedBackendWallet(wallet); - deleteDisclosure.onOpen(); + setDeleteOpen(true); }, text: "Delete", }, @@ -263,39 +297,46 @@ export const BackendWalletsTable: React.FC = ({ title="backend wallets" /> - {selectedBackendWallet && editDisclosure.isOpen && ( - + {selectedBackendWallet && ( + + + + + )} - {selectedBackendWallet && receiveDisclosure.isOpen && ( + {selectedBackendWallet && ( )} - {selectedBackendWallet && sendDisclosure.isOpen && ( + {selectedBackendWallet && ( )} - {selectedBackendWallet && deleteDisclosure.isOpen && ( + {selectedBackendWallet && ( )} @@ -303,30 +344,35 @@ export const BackendWalletsTable: React.FC = ({ ); }; -const EditModal = ({ +function EditModalContent({ backendWallet, - disclosure, + onOpenChange, instanceUrl, authToken, client, }: { backendWallet: BackendWallet; - disclosure: UseDisclosureReturn; + onOpenChange: (open: boolean) => void; instanceUrl: string; authToken: string; client: ThirdwebClient; -}) => { +}) { const updateBackendWallet = useEngineUpdateBackendWallet({ authToken, instanceUrl, }); - const [label, setLabel] = useState(backendWallet.label ?? ""); + const form = useForm({ + resolver: zodResolver(editWalletSchema), + defaultValues: { + label: backendWallet.label ?? "", + }, + }); - const onClick = async () => { + const onSubmit = async (data: EditWalletFormValues) => { const promise = updateBackendWallet.mutateAsync( { - label, + label: data.label, walletAddress: backendWallet.address, }, { @@ -334,7 +380,7 @@ const EditModal = ({ console.error(error); }, onSuccess: () => { - disclosure.onClose(); + onOpenChange(false); }, }, ); @@ -346,115 +392,144 @@ const EditModal = ({ }; return ( - - - - Update Backend Wallet - - -
- - Wallet Address +
+ + Update Backend Wallet + +
+ +
+
+

Wallet Address

- - - Label - setLabel(e.target.value)} - placeholder="Enter a description for this backend wallet" - type="text" - value={label} - /> - +
+ + ( + + Label + + + + + + )} + />
- - - - - - - - +
+ + +
+
+ +
); -}; +} -const ReceiveFundsModal = ({ +function ReceiveFundsModal({ backendWallet, - disclosure, - client, + open, + onOpenChange, }: { backendWallet: BackendWallet; - disclosure: UseDisclosureReturn; + open: boolean; + onOpenChange: (open: boolean) => void; client: ThirdwebClient; -}) => { - const qrCodeBase64Query = useQuery({ - // only run this if we have a backendWallet address - enabled: !!backendWallet.address, - // start out with empty string - placeholderData: "", - queryFn: async () => { - return new Promise((resolve, reject) => { - QRCode.toDataURL( - backendWallet.address, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - (error: any, dataUrl: string) => { - if (error) { - reject(error); - } else { - resolve(dataUrl); - } - }, - ); - }); - }, - queryKey: ["engine", "receive-funds-qr-code", backendWallet.address], - }); +}) { + // const qrCodeBase64Query = useQuery({ + // // only run this if we have a backendWallet address + // enabled: !!backendWallet.address, + // // start out with empty string + // placeholderData: "", + // queryFn: async () => { + // return new Promise((resolve, reject) => { + // QRCode.toDataURL( + // backendWallet.address, + // // biome-ignore lint/suspicious/noExplicitAny: FIXME + // (error: any, dataUrl: string) => { + // if (error) { + // reject(error); + // } else { + // resolve(dataUrl); + // } + // }, + // ); + // }); + // }, + // queryKey: ["engine", "receive-funds-qr-code", backendWallet.address], + // }); + + const qrCodeWidth = 300; + const isMobile = useIsMobile(); + const { theme } = useTheme(); + const isDarkMode = theme === "dark"; return ( - - - - Receive Funds - - -
- - Fund this address or QR code: - - - {/* eslint-disable-next-line @next/next/no-img-element */} - QR code for receiving funds + + + Receive Funds + Send funds to this address + + +
+
+
- - - +
+ + +
+ ); -}; - -interface SendFundsInput { - toAddress: string; - amount: number; } + const SendFundsModal = ({ fromWallet, backendWallets, - disclosure, + open, + onOpenChange, instanceUrl, authToken, chainId, @@ -462,13 +537,20 @@ const SendFundsModal = ({ }: { fromWallet: BackendWallet; backendWallets: BackendWallet[]; - disclosure: UseDisclosureReturn; + open: boolean; + onOpenChange: (open: boolean) => void; instanceUrl: string; authToken: string; chainId: number; client: ThirdwebClient; }) => { - const form = useForm(); + const form = useForm({ + resolver: zodResolver(sendFundsSchema), + defaultValues: { + toAddress: "", + amount: 0, + }, + }); const sendTokens = useEngineSendTokens({ authToken, instanceUrl, @@ -481,14 +563,14 @@ const SendFundsModal = ({ instanceUrl, }); const chain = idToChain.get(chainId); - const toWalletDisclosure = useDisclosure(); + const [toWalletOpen, setToWalletOpen] = useState(false); if (!backendWalletBalance) { return null; } - const onSubmit = async (data: SendFundsInput) => { - const promise = sendTokens.mutateAsync( + const onSubmit = async (data: SendFundsFormValues) => { + sendTokens.mutateAsync( { amount: data.amount, chainId: chainId, @@ -497,131 +579,172 @@ const SendFundsModal = ({ }, { onSuccess: () => { - disclosure.onClose(); + onOpenChange(false); + toast.success("Successfully sent a request to send funds."); + }, + onError: (error) => { + toast.error("Failed to send tokens.", { + description: parseError(error), + }); + console.error(error); }, }, ); - - toast.promise(promise, { - error: "Failed to send tokens.", - success: "Successfully sent a request to send funds.", - }); }; return ( - - - - Send Funds - - -
- - From - - - - To - {toWalletDisclosure.isOpen ? ( - - ) : ( - - )} - - - - - - Amount - - - - {chain?.nativeCurrency.symbol} - - - - Current amount:{" "} - {prettyPrintCurrency({ - amount: backendWalletBalance.displayValue, - symbol: backendWalletBalance.symbol, - })} - - - - Chain - - + + + Send Funds + Send funds to a backend wallet + + +
+ +
+
+

From

+ - {chain?.name} - - -
- +
- - - - - - + ( + + To + + {toWalletOpen ? ( + + ) : ( + + )} + + + + + )} + /> + ( + + Amount + +
+ +
+ {chain?.nativeCurrency.symbol} +
+
+
+
+ Current amount:{" "} + {prettyPrintCurrency({ + amount: backendWalletBalance.displayValue, + symbol: backendWalletBalance.symbol, + })} +
+ +
+ )} + /> + +
+

Chain

+
+ + {chain?.name} +
+
+
+ +
+ + +
+ + + + ); }; function DeleteModal({ backendWallet, - disclosure, + open, + onOpenChange, instanceUrl, authToken, client, }: { backendWallet: BackendWallet; - disclosure: UseDisclosureReturn; + open: boolean; + onOpenChange: (open: boolean) => void; instanceUrl: string; authToken: string; client: ThirdwebClient; @@ -636,54 +759,57 @@ function DeleteModal({ const [ackDeletion, setAckDeletion] = useState(false); const onClick = () => { - const promise = deleteBackendWallet.mutateAsync( + deleteBackendWallet.mutateAsync( { walletAddress: backendWallet.address }, { onError: (error) => { + toast.error("Failed to delete backend wallet.", { + description: parseError(error), + }); console.error(error); }, onSuccess: () => { - disclosure.onClose(); + toast.success("Successfully deleted backend wallet."); + onOpenChange(false); }, }, ); - - toast.promise(promise, { - error: "Failed to delete backend wallet.", - success: "Successfully deleted backend wallet.", - }); }; return ( - - - - Delete Backend Wallet - - -
- - Wallet Type -
- { - EngineBackendWalletOptions.find( - (opt) => opt.key === backendWallet.type, - )?.name - } -
-
- - Wallet Address - - + + + + Delete Backend Wallet + + Are you sure you want to delete this backend wallet? + + + +
+
+

Wallet Type

+ + { + EngineBackendWalletOptions.find( + (opt) => opt.key === backendWallet.type, + )?.name + } + +
+
+

Wallet Address

+
{isLocalWallet && ( - + This action is irreversible. @@ -699,10 +825,10 @@ function DeleteModal({ )} - +
- - - - - +
+ + ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx index 4d3099c5809..e9c16c91771 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx @@ -304,7 +304,7 @@ export const CreateBackendWalletButton: React.FC< Cancel + - - If this transaction is already submitted, it may complete before - the cancellation is submitted. - -
-
+ + + Cancel Transaction + + Are you sure you want to cancel this transaction? + + + +
+
+ +

{transaction.queueId}

+
+ +
+ +

+ {format(new Date(transaction.queuedAt ?? ""), "PP pp z")} +

+
+ +
+ + +
- - - - - - + className="-translate-x-1" + /> +
- - + {transaction.functionName && ( +
+ +

{transaction.functionName}

+
+ )} + +

+ If this transaction is already submitted, it may complete before the + cancellation is submitted. +

+
+ +
+ + +
+ + ); -}; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx index aedbe277d95..66a9ff8506f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx @@ -1,29 +1,18 @@ -import { Collapse, Divider, useDisclosure } from "@chakra-ui/react"; -import { LinkButton } from "chakra/button"; -import { FormLabel } from "chakra/form"; -import { Text } from "chakra/text"; import { format, formatDistanceToNowStrict } from "date-fns"; import { + ArrowLeftIcon, + ArrowRightIcon, ExternalLinkIcon, InfoIcon, - MoveLeftIcon, - MoveRightIcon, } from "lucide-react"; import Link from "next/link"; -import { - type Dispatch, - type SetStateAction, - useId, - useMemo, - useState, -} from "react"; +import { useId, useMemo, useState } from "react"; import { type ThirdwebClient, toTokens } from "thirdweb"; import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart"; import { PaginationButtons } from "@/components/blocks/pagination-buttons"; import { WalletAddress } from "@/components/blocks/wallet-address"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { CopyAddressButton } from "@/components/ui/CopyAddressButton"; import { CopyTextButton } from "@/components/ui/CopyTextButton"; import type { ChartConfig } from "@/components/ui/chart"; import { Label } from "@/components/ui/label"; @@ -116,7 +105,6 @@ export function TransactionsTable(props: { authToken: string; client: ThirdwebClient; }) { - const transactionDisclosure = useDisclosure(); const [selectedTransaction, setSelectedTransaction] = useState(null); const [autoUpdate, setAutoUpdate] = useState(true); @@ -149,6 +137,8 @@ export function TransactionsTable(props: { (transactionsQuery.isPlaceholderData && transactionsQuery.isFetching) || (transactionsQuery.isLoading && !transactionsQuery.isPlaceholderData); + const [showTxDetailsSheet, setShowTxDetailsSheet] = useState(false); + return (
@@ -205,15 +195,21 @@ export function TransactionsTable(props: { key={`${tx.queueId}${tx.chainId}${tx.blockNumber}`} onClick={() => { setSelectedTransaction(tx); - transactionDisclosure.onOpen(); + setShowTxDetailsSheet(true); }} > {/* Queue ID */} - @@ -274,7 +270,7 @@ export function TransactionsTable(props: {
)} - {transactionDisclosure.isOpen && selectedTransaction && ( + {selectedTransaction && ( setSelectedTransaction(transactions[idx - 1] || null) : undefined } - setSelectedTransaction={setSelectedTransaction} + open={showTxDetailsSheet} + setOpen={setShowTxDetailsSheet} transaction={selectedTransaction} /> )} @@ -433,7 +430,7 @@ function TxHashCell(props: { transaction: Transaction }) { if (!explorer) { return ( @@ -459,7 +456,7 @@ function TxHashCell(props: { transaction: Transaction }) { target="_blank" > {shortHash}{" "} - + ); @@ -572,22 +569,22 @@ const TransactionDetailsDrawer = ({ instanceUrl, onClickPrevious, onClickNext, - setSelectedTransaction, authToken, client, + open, + setOpen, }: { transaction: Transaction; instanceUrl: string; onClickPrevious?: () => void; onClickNext?: () => void; - setSelectedTransaction: Dispatch>; authToken: string; client: ThirdwebClient; + open: boolean; + setOpen: (open: boolean) => void; }) => { const { idToChain } = useAllChainsData(); - const errorMessageDisclosure = useDisclosure(); - const advancedTxDetailsDisclosure = useDisclosure(); - + const [errorMessageOpen, setErrorMessageOpen] = useState(false); if (!transaction.chainId || !transaction.status) { return null; } @@ -615,262 +612,321 @@ const TransactionDetailsDrawer = ({ } } - const handleOpenChange = (isOpen: boolean) => { - setSelectedTransaction(isOpen ? transaction : null); - }; - return ( - - - - - Transaction Details {status.name} + + + + + Transaction Details{" "} + {status.name} -
-
- Queue ID - {transaction.queueId} -
-
- Chain -
- + {/* Queue ID */} + {transaction.queueId && ( +
+

Queue ID

+ - {chain?.name}
-
+ )} + {/* Chain */} + {chain && ( +
+

Chain

+
+ + {chain.name} +
+
+ )} + + {/* Function */} {functionCalled && ( -
- Function - {functionCalled} +
+

Function

+

+ {functionCalled} +

)} -
- - {transaction.accountAddress ? "Signer Address" : "From Address"} - - - {transaction.fromAddress} - -
+ {/* From Address */} + {transaction.fromAddress && ( +
+

+ {transaction.accountAddress ? "Signer Address" : "From Address"} +

+ +
+ )} + {/* Account Address */} {transaction.accountAddress && ( -
- Account Address - +

Account Address

+
)} -
- {/* The "to" address is usually a contract except for native token transfers. */} - - {functionCalled === "transfer" - ? "Recipient Address" - : "Contract Address"} - - - {transaction.toAddress} - -
- - {transaction.errorMessage && ( -
- Error - - {transaction.errorMessage} - -
)} - - - - - + {/* Error */} + {transaction.errorMessage && ( +
+

Error

+

+ {errorMessageOpen + ? transaction.errorMessage + : transaction.errorMessage.length > 150 + ? `${transaction.errorMessage.slice(0, 150)}...` + : transaction.errorMessage} +

+ {transaction.errorMessage.length > 150 && ( + + )} +
+ )} - {/* On-chain details */} +
+ +
-
-
- Value +
+
+

Value

- +
- +

{transaction.value ? toTokens(BigInt(transaction.value), decimals) : 0}{" "} {symbol} - +

{transaction.transactionHash && ( <> -
- Transaction Hash - +

Transaction Hash

+
-
- Transaction Fee - {txFeeDisplay} +
+

Transaction Fee

+

{txFeeDisplay}

- -
- {transaction.nonce && ( -
-
- Nonce - - - -
- {transaction.nonce ?? "N/A"} +
+ {typeof transaction.nonce === "number" && ( +
+
+

Nonce

+ + +
- )} - - {transaction.gasLimit && ( -
-
- Gas Units - - - -
- - {Number(transaction.gasLimit).toLocaleString()} - +

{transaction.nonce ?? "N/A"}

+
+ )} + + {transaction.gasLimit && ( +
+
+

Gas Units

+ + +
- )} - - {transaction.gasPrice && ( -
-
- Gas Price - - - -
- - {Number(transaction.gasPrice).toLocaleString()} - +

+ {Number(transaction.gasLimit).toLocaleString()} +

+
+ )} + + {transaction.gasPrice && ( +
+
+

Gas Price

+ + +
- )} - - {transaction.blockNumber && ( -
- Block - + {Number(transaction.gasPrice).toLocaleString()} +

+
+ )} + + {transaction.blockNumber && ( +
+

Block

+
- )} -
- - - + {transaction.blockNumber} + + +
+ )} +
)}
-
+ + {/* footer */} +
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6779f1ebe65..32023e40fa1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,9 +229,6 @@ importers: prettier: specifier: 3.6.2 version: 3.6.2 - qrcode: - specifier: 1.5.3 - version: 1.5.3 react: specifier: 19.1.0 version: 19.1.0 @@ -256,6 +253,9 @@ importers: react-markdown: specifier: 10.1.0 version: 10.1.0(@types/react@19.1.8)(react@19.1.0) + react-qrcode-logo: + specifier: ^3.0.0 + version: 3.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-table: specifier: ^7.8.0 version: 7.8.0(react@19.1.0) @@ -350,9 +350,6 @@ importers: '@types/pluralize': specifier: ^0.0.33 version: 0.0.33 - '@types/qrcode': - specifier: 1.5.5 - version: 1.5.5 '@types/react': specifier: 19.1.8 version: 19.1.8 @@ -11510,6 +11507,10 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -13265,6 +13266,9 @@ packages: resolution: {integrity: sha512-s0/5XkAWe0/dWISiljdrybjwDCHhgN31Nu/wznOZPKeikgcJtZtbvPKBz0t802XWqfSQnQDt3L6xiAE5JLlfuw==} engines: {node: '>=18'} + qrcode-generator@1.5.2: + resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==} + qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true @@ -13528,6 +13532,12 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + react-qrcode-logo@3.0.0: + resolution: {integrity: sha512-2+vZ3GNBdUpYxIKyt6SFZsDGXa0xniyUQ0wPI4O0hJTzRjttPIx1pPnH9IWQmp/4nDMoN47IBhi3Breu1KudYw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + react-redux@9.2.0: resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: @@ -31428,6 +31438,8 @@ snapshots: lodash.isarguments@3.1.0: {} + lodash.isequal@4.5.0: {} + lodash.merge@4.6.2: {} lodash.mergewith@4.6.2: {} @@ -33635,6 +33647,8 @@ snapshots: - supports-color - utf-8-validate + qrcode-generator@1.5.2: {} + qrcode-terminal@0.11.0: {} qrcode@1.5.3: @@ -33986,6 +34000,13 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tinycolor2: 1.6.0 + react-qrcode-logo@3.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + lodash.isequal: 4.5.0 + qrcode-generator: 1.5.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6