From 25ea992c69a9af743fda6270521d79753853d95b Mon Sep 17 00:00:00 2001 From: davidjhonsonmate Date: Tue, 20 Oct 2026 15:48:50 +0100 Subject: [PATCH] fix: fix localStorage sync, revoke object URLs, static imports, xdr shadowing - use-local-storage.ts: handle null newValue (removal events) by resetting to initialValue (#493) - avatar-upload.tsx: revoke object URL on cancel, new selection, and unmount (#492) - connect/page.tsx: replace dynamic imports with static imports (#491) - transactions.ts: rename xdr parameter to txXdr to avoid shadowing imported xdr module (#490) --- src/app/(auth)/connect/page.tsx | 6 ++--- src/components/shared/avatar-upload.tsx | 34 +++++++++++++++++++++++-- src/lib/hooks/use-local-storage.ts | 15 +++++++---- src/lib/stellar/transactions.ts | 4 +-- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/app/(auth)/connect/page.tsx b/src/app/(auth)/connect/page.tsx index e9dca42..b387275 100644 --- a/src/app/(auth)/connect/page.tsx +++ b/src/app/(auth)/connect/page.tsx @@ -8,6 +8,8 @@ import { Wallet, Shield, Loader2, AlertCircle } from "lucide-react"; import { isFreighterInstalled } from "@/lib/stellar/wallet"; import { useEffect, useRef, useState } from "react"; import { useToastContext } from "@/components/shared/toast"; +import { getProfile } from "@/lib/api/auth"; +import { useAuthStore } from "@/store/auth-store"; /** * ConnectPage component handles the Freighter wallet connection flow. @@ -45,8 +47,6 @@ export default function ConnectPage() { let active = true; const redirect = async () => { try { - const { getProfile } = await import("@/lib/api/auth"); - const { useAuthStore } = await import("@/store/auth-store"); const jwt = useAuthStore.getState().jwt; const profile = jwt ? await getProfile(jwt) : null; if (active) { @@ -72,8 +72,6 @@ export default function ConnectPage() { } try { - const { getProfile } = await import("@/lib/api/auth"); - const { useAuthStore } = await import("@/store/auth-store"); const jwt = useAuthStore.getState().jwt; const profile = jwt ? await getProfile(jwt) : null; addToast("Wallet connected successfully!", "success"); diff --git a/src/components/shared/avatar-upload.tsx b/src/components/shared/avatar-upload.tsx index d6244fc..049d667 100644 --- a/src/components/shared/avatar-upload.tsx +++ b/src/components/shared/avatar-upload.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useCallback, useRef } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import Cropper, { Area } from 'react-easy-crop'; import { Camera, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -72,8 +72,18 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP const [isUploading, setIsUploading] = useState(false); const [isCropDialogOpen, setIsCropDialogOpen] = useState(false); const fileInputRef = useRef(null); + const imageUrlRef = useRef(null); const { addToast } = useToastContext(); + // Cleanup object URL on unmount + useEffect(() => { + return () => { + if (imageUrlRef.current) { + URL.revokeObjectURL(imageUrlRef.current); + } + }; + }, []); + const onFileChange = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const file = e.target.files[0]; @@ -85,7 +95,12 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP addToast('Image must be less than 5MB', 'error'); return; } + // Revoke previous object URL if exists + if (imageUrlRef.current) { + URL.revokeObjectURL(imageUrlRef.current); + } const imageDataUrl = URL.createObjectURL(file); + imageUrlRef.current = imageDataUrl; setImageSrc(imageDataUrl); setIsCropDialogOpen(true); } @@ -105,6 +120,11 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP await onUpload(croppedImageFile); addToast('Avatar updated successfully', 'success'); setIsCropDialogOpen(false); + // Revoke object URL and reset state + if (imageUrlRef.current) { + URL.revokeObjectURL(imageUrlRef.current); + imageUrlRef.current = null; + } setImageSrc(null); } } catch (e) { @@ -140,7 +160,17 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP /> - + { + if (!open) { + // Cleanup object URL when dialog closes + if (imageUrlRef.current) { + URL.revokeObjectURL(imageUrlRef.current); + imageUrlRef.current = null; + } + setImageSrc(null); + } + setIsCropDialogOpen(open); + }}> Crop Image diff --git a/src/lib/hooks/use-local-storage.ts b/src/lib/hooks/use-local-storage.ts index 56d7755..ebdd2e1 100644 --- a/src/lib/hooks/use-local-storage.ts +++ b/src/lib/hooks/use-local-storage.ts @@ -61,11 +61,16 @@ export function useLocalStorage( if (typeof window === "undefined" || !isMounted) return; const handleStorageChange = (e: StorageEvent) => { - if (e.key === key && e.newValue) { - try { - setStoredValue(JSON.parse(e.newValue)); - } catch (error) { - console.error(`useLocalStorage sync error for key "${key}":`, error); + if (e.key === key) { + if (e.newValue) { + try { + setStoredValue(JSON.parse(e.newValue)); + } catch (error) { + console.error(`useLocalStorage sync error for key "${key}":`, error); + } + } else { + // Reset to initial value when another tab removes the item + setStoredValue(initialValue); } } }; diff --git a/src/lib/stellar/transactions.ts b/src/lib/stellar/transactions.ts index 5381c14..d8cedd6 100644 --- a/src/lib/stellar/transactions.ts +++ b/src/lib/stellar/transactions.ts @@ -111,14 +111,14 @@ export async function buildContractInvokeTx({ /** Sign an XDR transaction with the selected wallet and submit it to Stellar RPC. */ export async function signAndSubmit( - xdr: string, + txXdr: string, network: NetworkType, providerId: WalletProviderId = getSelectedWalletId(), ): Promise { const passphrase = getNetworkPassphrase(network); try { - const signedXdr = await signWalletTransaction(xdr, network, providerId); + const signedXdr = await signWalletTransaction(txXdr, network, providerId); const signedTransaction = TransactionBuilder.fromXDR(signedXdr, passphrase); const response = await new rpc.Server(getRpcUrl(network)).sendTransaction(signedTransaction);