Skip to content
Open
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
40 changes: 40 additions & 0 deletions NetworkBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { AlertCircle, Globe } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

const SUPPORTED_NETWORKS: Record<number, string> = {
1: 'Ethereum',
137: 'Polygon',
56: 'BSC',
};

export const NetworkBadge: React.FC<{ chainId: number | null }> = ({ chainId }) => {
const isSupported = chainId !== null && !!SUPPORTED_NETWORKS[chainId];
const networkName = isSupported ? SUPPORTED_NETWORKS[chainId!] : 'Unsupported Network';

if (!isSupported) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="destructive" className="flex items-center gap-1 cursor-help">
<AlertCircle className="h-3 w-3" />
<span className="hidden sm:inline">Unsupported</span>
</Badge>
</TooltipTrigger>
<TooltipContent>
<p>Please switch to Ethereum, Polygon, or BSC</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

return (
<Badge variant="secondary" className="flex items-center gap-1 bg-green-50 text-green-700 hover:bg-green-100 border-green-200">
<Globe className="h-3 w-3" />
<span>{networkName}</span>
</Badge>
);
};
114 changes: 114 additions & 0 deletions WalletConnector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import { useWalletStore } from '@/store/walletStore';
import { useWalletConnector } from '@/hooks/useWalletConnector';
import { Button } from '@/components/ui/button';
import { WalletModal } from '@/components/WalletModal';
import { NetworkBadge } from './NetworkBadge';
import { CopyButton } from '@/components/ui/CopyButton';
import {
Loader2,
LogOut,
Wallet,
ChevronDown,
ShieldCheck
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator
} from '@/components/ui/dropdown-menu';

export const WalletConnector: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { isConnected, address, chainId, disconnect, isConnecting } = useWalletStore();
const { isLoadingConnector } = useWalletConnector();

// Show connecting state if either the chunk is loading or the wallet is authenticating
const isPending = isConnecting || isLoadingConnector;

const handleDisconnect = () => {
disconnect();
};

const formatAddress = (addr: string) => {
return `${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}`;
};

if (!isConnected) {
return (
<>
<Button
onClick={() => setIsModalOpen(true)}
disabled={isPending}
className="flex items-center gap-2"
>
{isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Wallet className="h-4 w-4" />
)}
{isPending ? 'Connecting...' : 'Connect Wallet'}
</Button>

<WalletModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
/>
</>
);
}

return (
<div className="flex items-center gap-2">
{/* Network detection happens inside the Badge */}
<NetworkBadge chainId={chainId} />

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="flex items-center gap-2 px-3">
<div className="flex flex-col items-end hidden sm:flex">
<span className="text-xs font-medium text-muted-foreground uppercase">Connected</span>
<span className="text-sm font-mono">{formatAddress(address!)}</span>
</div>
<div className="sm:hidden">
<Wallet className="h-4 w-4" />
</div>
<ChevronDown className="h-3 w-3 opacity-50" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent align="end" className="w-56">
<div className="flex items-center justify-between p-2">
<div className="flex flex-col">
<span className="text-xs text-muted-foreground">Wallet Address</span>
<span className="text-sm font-mono truncate max-w-[140px]">{address}</span>
</div>
<CopyButton text={address!} variant="icon" />
</div>

<DropdownMenuSeparator />

<DropdownMenuItem className="flex items-center gap-2 cursor-default">
<ShieldCheck className="h-4 w-4 text-green-500" />
<div className="flex flex-col">
<span className="text-sm">KYC Status</span>
<span className="text-xs text-green-500 font-medium">Verified</span>
</div>
</DropdownMenuItem>

<DropdownMenuSeparator />

<DropdownMenuItem
onClick={handleDisconnect}
className="text-destructive focus:text-destructive flex items-center gap-2 cursor-pointer"
>
<LogOut className="h-4 w-4" />
<span>Disconnect</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
56 changes: 56 additions & 0 deletions walletStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface WalletState {
address: string | null;
chainId: number | null;
isConnected: boolean;
isConnecting: boolean;
error: string | null;

// Actions
setConnection: (address: string, chainId: number) => void;
setConnecting: (isConnecting: boolean) => void;
setError: (error: string | null) => void;
disconnect: () => void;
updateChainId: (chainId: number) => void;
}

export const useWalletStore = create<WalletState>()(
persist(
(set) => ({
address: null,
chainId: null,
isConnected: false,
isConnecting: false,
error: null,

setConnection: (address, chainId) => set({
address,
chainId,
isConnected: true,
isConnecting: false,
error: null,
}),

setConnecting: (isConnecting) => set({ isConnecting }),

setError: (error) => set({ error, isConnecting: false }),

updateChainId: (chainId) => set({ chainId }),

disconnect: () => set({
address: null,
chainId: null,
isConnected: false,
isConnecting: false,
error: null,
}),
}),
{
name: 'propchain-wallet-storage',
// Only persist connection info, not transient loading/error states
partialize: (state) => ({ address: state.address, chainId: state.chainId, isConnected: state.isConnected }),
}
)
);