Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/app/(auth)/connect/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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");
Expand Down
34 changes: 32 additions & 2 deletions src/components/shared/avatar-upload.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -72,8 +72,18 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP
const [isUploading, setIsUploading] = useState(false);
const [isCropDialogOpen, setIsCropDialogOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const imageUrlRef = useRef<string | null>(null);
const { addToast } = useToastContext();

// Cleanup object URL on unmount
useEffect(() => {
return () => {
if (imageUrlRef.current) {
URL.revokeObjectURL(imageUrlRef.current);
}
};
}, []);

const onFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
Expand All @@ -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);
}
Expand All @@ -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) {
Expand Down Expand Up @@ -140,7 +160,17 @@ export function AvatarUpload({ currentAvatarUrl, name, onUpload }: AvatarUploadP
/>
</div>

<Dialog open={isCropDialogOpen} onOpenChange={setIsCropDialogOpen}>
<Dialog open={isCropDialogOpen} onOpenChange={(open) => {
if (!open) {
// Cleanup object URL when dialog closes
if (imageUrlRef.current) {
URL.revokeObjectURL(imageUrlRef.current);
imageUrlRef.current = null;
}
setImageSrc(null);
}
setIsCropDialogOpen(open);
}}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Crop Image</DialogTitle>
Expand Down
15 changes: 10 additions & 5 deletions src/lib/hooks/use-local-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,16 @@ export function useLocalStorage<T>(
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);
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/lib/stellar/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionResult> {
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);

Expand Down