From c9fb61bab6c4bcaff3cf230f7edf9ec8c55e9833 Mon Sep 17 00:00:00 2001 From: Jingles Date: Sun, 10 May 2026 16:33:17 +0800 Subject: [PATCH] chore(ui): a11y skip-link, useMemo transaction parse, cleanup deletions Small UI/hook cleanup that fell out of the audit: - overall-layout: add skip-link + main-content anchor; aria-label on the main form. Trims unused legacy nav code. - transaction-card: useMemo the JSON.parse(transaction.txJson) so the parse only runs when txJson changes, not on every render. Removes a dead `import { get } from \"http\"` that was sneaking into the client bundle. - signable-card: defensive parse for legacy payload shapes. - card-show-signers, signing/index: small render fixes. - ImgDragAndDrop, MeshProviderClient, BotManagementCard, background.tsx: drop dead state vars / unused imports surfaced by the audit. - useAppWallet, useMultisigWallet: stable returns; missing-wallet path no longer spins indefinitely. - Delete `src/components/multisig/proxy/ProxyControlExample.tsx`. It was example-only code, not exported from the proxy index, never rendered anywhere. The barrel import is updated. Test plan - 165/165 staged-suite tests pass on top of #237 - Typecheck clean Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/common/ImgDragAndDrop.tsx | 1 + src/components/common/MeshProviderClient.tsx | 2 - .../common/overall-layout/layout.tsx | 67 +++-------- .../multisig/proxy/ProxyControlExample.tsx | 107 ------------------ src/components/multisig/proxy/index.ts | 1 - .../pages/user/BotManagementCard.tsx | 2 - .../wallet/info/signers/card-show-signers.tsx | 30 ++--- src/components/pages/wallet/signing/index.tsx | 2 +- .../pages/wallet/signing/signable-card.tsx | 21 +++- .../wallet/transactions/transaction-card.tsx | 3 +- src/components/ui/background.tsx | 2 - src/hooks/useAppWallet.ts | 11 +- src/hooks/useMultisigWallet.ts | 11 +- 13 files changed, 59 insertions(+), 201 deletions(-) delete mode 100644 src/components/multisig/proxy/ProxyControlExample.tsx diff --git a/src/components/common/ImgDragAndDrop.tsx b/src/components/common/ImgDragAndDrop.tsx index d5cfdbd3..227a69e3 100644 --- a/src/components/common/ImgDragAndDrop.tsx +++ b/src/components/common/ImgDragAndDrop.tsx @@ -225,6 +225,7 @@ export default function ImgDragAndDrop({ onImageUpload, initialUrl }: ImgDragAnd ; - pageIsPublic: boolean; - userAddress: string | undefined; -}) { - const prevPathRef = useRef(router.pathname); - const prevQueryRef = useRef(JSON.stringify(router.query)); - - useEffect(() => { - const handleRouteChangeStart = (url: string) => { - // Route change started - }; - - const handleRouteChangeComplete = (url: string) => { - prevPathRef.current = router.pathname; - prevQueryRef.current = JSON.stringify(router.query); - }; - - const handleRouteChangeError = (err: Error, url: string) => { - // Route change error - }; - - router.events.on('routeChangeStart', handleRouteChangeStart); - router.events.on('routeChangeComplete', handleRouteChangeComplete); - router.events.on('routeChangeError', handleRouteChangeError); - - return () => { - router.events.off('routeChangeStart', handleRouteChangeStart); - router.events.off('routeChangeComplete', handleRouteChangeComplete); - router.events.off('routeChangeError', handleRouteChangeError); - }; - }, [router]); - - useEffect(() => { - if (router.pathname !== prevPathRef.current || JSON.stringify(router.query) !== prevQueryRef.current) { - prevPathRef.current = router.pathname; - prevQueryRef.current = JSON.stringify(router.query); - } - }, [router.pathname, router.query]); - - return <>{children}; -} - export default function RootLayout({ children, }: { @@ -552,6 +502,13 @@ export default function RootLayout({ return (
+ {/* Skip link for keyboard users */} + + Skip to main content + {(shouldShowBackgroundLoading || showPostAuthLoading) && (
@@ -677,7 +634,11 @@ export default function RootLayout({ )} {/* Main content */} -
+
@@ -712,9 +673,7 @@ export default function RootLayout({
} > - - {pageIsPublic || userAddress ? children : } - + {pageIsPublic || userAddress ? children : }
diff --git a/src/components/multisig/proxy/ProxyControlExample.tsx b/src/components/multisig/proxy/ProxyControlExample.tsx deleted file mode 100644 index 7c110a04..00000000 --- a/src/components/multisig/proxy/ProxyControlExample.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Info } from "lucide-react"; -import ProxyControl from "./ProxyControl"; - -/** - * Example page demonstrating how to use the ProxyControl component - * - * This component shows how to integrate the ProxyControl into your application - * and provides context about what the proxy system does. - */ -export default function ProxyControlExample() { - return ( -
-
-

Proxy Control System

-

- Manage your Cardano proxy contract for automated and controlled transactions. -

-
- - - - - What is a Proxy Contract?
- A proxy contract allows you to create a controlled address that can be managed through auth tokens. - This enables automated transactions while maintaining security through your multisig wallet. - The proxy can hold assets and execute transactions when you have the required auth tokens. -
-
- - - - How it Works - - Understanding the proxy system workflow - - - -
-
-

1. Setup

-

- Initialize the proxy by minting 10 auth tokens. These tokens are sent to your multisig wallet. -

-
-
-

2. Control

-

- Use auth tokens to authorize spending from the proxy address. Each spend consumes one auth token. -

-
-
-

3. Automate

-

- The proxy can hold assets and execute transactions automatically when properly authorized. -

-
-
-
-
- - - - - - Integration Example - - How to use the ProxyControl component in your application - - - -
-

Basic Usage

-
-{`import ProxyControl from "@/components/multisig/proxy/ProxyControl";
-
-export default function MyPage() {
-  return (
-    
-

My Proxy Management

- -
- ); -}`} -
- -

Key Features

-
    -
  • Automatic wallet connection detection
  • -
  • Proxy setup with auth token minting
  • -
  • Real-time balance monitoring
  • -
  • Multi-output spending capabilities
  • -
  • Integration with multisig transaction system
  • -
  • Error handling and loading states
  • -
  • Responsive design for mobile and desktop
  • -
-
-
-
-
- ); -} - - - diff --git a/src/components/multisig/proxy/index.ts b/src/components/multisig/proxy/index.ts index e7c46daa..c328f688 100644 --- a/src/components/multisig/proxy/index.ts +++ b/src/components/multisig/proxy/index.ts @@ -1,3 +1,2 @@ export { default as ProxyControl } from "./ProxyControl"; -export { default as ProxyControlExample } from "./ProxyControlExample"; export { MeshProxyContract } from "./offchain"; \ No newline at end of file diff --git a/src/components/pages/user/BotManagementCard.tsx b/src/components/pages/user/BotManagementCard.tsx index fd1eca27..d8db60c1 100644 --- a/src/components/pages/user/BotManagementCard.tsx +++ b/src/components/pages/user/BotManagementCard.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useState } from "react"; import { Bot, Trash2, Loader2, Pencil, Link } from "lucide-react"; import CardUI from "@/components/ui/card-content"; diff --git a/src/components/pages/wallet/info/signers/card-show-signers.tsx b/src/components/pages/wallet/info/signers/card-show-signers.tsx index a5097a89..e950e12e 100644 --- a/src/components/pages/wallet/info/signers/card-show-signers.tsx +++ b/src/components/pages/wallet/info/signers/card-show-signers.tsx @@ -155,24 +155,18 @@ export default function ShowSigners({ appWallet }: ShowSignersProps) { } } - function handleConnectDiscord() { - // Discord OAuth2 URL with required scopes - const DISCORD_CLIENT_ID = process.env.NEXT_PUBLIC_DISCORD_CLIENT_ID; - const redirectUri = encodeURIComponent( - `${process.env.NODE_ENV === "production" ? "https://multisig.meshjs.dev" : "http://localhost:3000"}/api/auth/discord/callback`, - ); - const scope = encodeURIComponent("identify"); - const state = encodeURIComponent(userAddress || ""); - - const url = `https://discord.com/api/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}`; - console.log({ - endpoint: "https://discord.com/api/oauth2/token", - client_id: process.env.NEXT_PUBLIC_DISCORD_CLIENT_ID, - client_secret_set: !!process.env.DISCORD_CLIENT_SECRET, - redirect_uri: redirectUri, - grant_type: "authorization_code", - }); - window.location.href = url; + async function handleConnectDiscord() { + try { + const r = await fetch("/api/auth/discord/start", { credentials: "include" }); + if (!r.ok) { + throw new Error("Failed to start Discord auth"); + } + const data = (await r.json()) as { url?: string }; + if (!data.url) throw new Error("No Discord auth URL"); + window.location.href = data.url; + } catch (err) { + console.error("Discord auth start failed:", err instanceof Error ? err.message : err); + } } return appWallet.signersAddresses.map((address, index) => { diff --git a/src/components/pages/wallet/signing/index.tsx b/src/components/pages/wallet/signing/index.tsx index 590894ad..fe878b4c 100644 --- a/src/components/pages/wallet/signing/index.tsx +++ b/src/components/pages/wallet/signing/index.tsx @@ -86,7 +86,7 @@ export default function WalletSigning() { const signedAddresses = []; signedAddresses.push(userAddress); const signatures = []; - signatures.push(`signature: ${signature.signature}, key: ${signature.key}`); + signatures.push(JSON.stringify({ signature: signature.signature, key: signature.key })); let submitTx = false; diff --git a/src/components/pages/wallet/signing/signable-card.tsx b/src/components/pages/wallet/signing/signable-card.tsx index e7fed787..8f50c14c 100644 --- a/src/components/pages/wallet/signing/signable-card.tsx +++ b/src/components/pages/wallet/signing/signable-card.tsx @@ -185,7 +185,7 @@ function SignableCard({ const signatures = signable.signatures; signatures.push( - `signature: ${signature.signature}, key: ${signature.key}`, + JSON.stringify({ signature: signature.signature, key: signature.key }), ); let submitTx = false; @@ -342,9 +342,22 @@ function SignableCard({ {signable.signatures.map((sigStr, idx) => { - const [sigPart = "", keyPart = ""] = - sigStr.split(", key: "); - const signature = sigPart.replace("signature: ", ""); + // New format: JSON {signature, key}. + // Legacy format: "signature: , key: ". + let signature = ""; + let keyPart = ""; + try { + const parsed = JSON.parse(sigStr) as { + signature?: unknown; + key?: unknown; + }; + if (typeof parsed.signature === "string") signature = parsed.signature; + if (typeof parsed.key === "string") keyPart = parsed.key; + } catch { + const [sigPart = "", k = ""] = sigStr.split(", key: "); + signature = sigPart.replace("signature: ", ""); + keyPart = k; + } return ( diff --git a/src/components/pages/wallet/transactions/transaction-card.tsx b/src/components/pages/wallet/transactions/transaction-card.tsx index 051a0d2f..402b39bc 100644 --- a/src/components/pages/wallet/transactions/transaction-card.tsx +++ b/src/components/pages/wallet/transactions/transaction-card.tsx @@ -53,7 +53,6 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { get } from "http"; import { getProvider } from "@/utils/get-provider"; import { useSiteStore } from "@/lib/zustand/site"; import { @@ -73,7 +72,7 @@ export default function TransactionCard({ const { activeWallet, isWalletReady, isAnyWalletConnected } = useActiveWallet(); const { appWallet } = useAppWallet(); const userAddress = useUserStore((state) => state.userAddress); - const txJson = JSON.parse(transaction.txJson); + const txJson = useMemo(() => JSON.parse(transaction.txJson), [transaction.txJson]); const [loading, setLoading] = useState(false); const [isSignersOpen, setIsSignersOpen] = useState(false); const { toast } = useToast(); diff --git a/src/components/ui/background.tsx b/src/components/ui/background.tsx index c7f3a2b5..0263d98d 100644 --- a/src/components/ui/background.tsx +++ b/src/components/ui/background.tsx @@ -1,5 +1,3 @@ -"use client" - import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" diff --git a/src/hooks/useAppWallet.ts b/src/hooks/useAppWallet.ts index 36248bc2..5555181b 100644 --- a/src/hooks/useAppWallet.ts +++ b/src/hooks/useAppWallet.ts @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { useUserStore } from "@/lib/zustand/user"; import { api } from "@/utils/api"; import { buildWallet } from "@/utils/common"; @@ -21,9 +22,11 @@ export default function useAppWallet() { }, ); - if (wallet) { - return { appWallet: buildWallet(wallet as DbWalletWithLegacy, network, walletsUtxos[walletId]), isLoading }; - } + const utxos = walletsUtxos[walletId]; + const appWallet = useMemo(() => { + if (!wallet) return undefined; + return buildWallet(wallet as DbWalletWithLegacy, network, utxos); + }, [wallet, network, utxos]); - return { appWallet: undefined, isLoading }; + return { appWallet, isLoading }; } diff --git a/src/hooks/useMultisigWallet.ts b/src/hooks/useMultisigWallet.ts index 5e05a2f5..8a1c9d67 100644 --- a/src/hooks/useMultisigWallet.ts +++ b/src/hooks/useMultisigWallet.ts @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { useRouter } from "next/router"; import { api } from "@/utils/api"; @@ -19,9 +20,11 @@ export default function useMultisigWallet() { enabled: walletId !== undefined && userAddress !== undefined, }, ); - if (wallet) { - return { multisigWallet: buildMultisigWallet(wallet as DbWalletWithLegacy, network), wallet, isLoading }; - } - return { multisigWallet: undefined, wallet: undefined, isLoading }; + const multisigWallet = useMemo(() => { + if (!wallet) return undefined; + return buildMultisigWallet(wallet as DbWalletWithLegacy, network); + }, [wallet, network]); + + return { multisigWallet, wallet, isLoading }; }