import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
getWalletBySource,
isWalletInstalled,
Wallet,
WalletAccount
} from '@subwallet/wallet-connect';
import { createWidget, DotPassportClient, type UserScores } from '@dotpassport/sdk';
interface SubWalletDotPassportProps {
apiKey: string;
}
export function SubWalletDotPassport({ apiKey }: SubWalletDotPassportProps) {
const [isInstalled, setIsInstalled] = useState<boolean>(false);
const [wallet, setWallet] = useState<Wallet | null>(null);
const [accounts, setAccounts] = useState<WalletAccount[]>([]);
const [selectedAccount, setSelectedAccount] = useState<WalletAccount | null>(null);
const [scores, setScores] = useState<UserScores | null>(null);
const [isConnecting, setIsConnecting] = useState(false);
const [error, setError] = useState<string | null>(null);
const widgetRef = useRef<ReturnType<typeof createWidget>>();
const widgetContainerRef = useRef<HTMLDivElement>(null);
const unsubscribeRef = useRef<(() => void) | null>(null);
// Check installation on mount
useEffect(() => {
isWalletInstalled('subwallet-js').then(setIsInstalled);
}, []);
// Connect to SubWallet
const connectWallet = useCallback(async () => {
setIsConnecting(true);
setError(null);
try {
const subwallet = await getWalletBySource('subwallet-js');
if (!subwallet) {
throw new Error('SubWallet is not installed');
}
await subwallet.enable();
const userAccounts = await subwallet.getAccounts();
if (userAccounts.length === 0) {
throw new Error('No accounts found');
}
setWallet(subwallet);
setAccounts(userAccounts);
setSelectedAccount(userAccounts[0]);
// Subscribe to account changes
unsubscribeRef.current = await subwallet.subscribeAccounts((newAccounts) => {
setAccounts(newAccounts);
if (newAccounts.length > 0) {
const currentAddress = selectedAccount?.address;
const stillExists = newAccounts.find(a => a.address === currentAddress);
if (!stillExists) {
setSelectedAccount(newAccounts[0]);
}
}
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to connect');
} finally {
setIsConnecting(false);
}
}, [selectedAccount?.address]);
// Fetch reputation data
useEffect(() => {
if (!selectedAccount) return;
const client = new DotPassportClient({ apiKey });
client.getScores(selectedAccount.address)
.then(setScores)
.catch(err => console.error('Failed to fetch scores:', err));
}, [selectedAccount, apiKey]);
// Mount/update widget
useEffect(() => {
if (!selectedAccount || !widgetContainerRef.current) return;
if (widgetRef.current) {
widgetRef.current.update({ address: selectedAccount.address });
} else {
widgetRef.current = createWidget({
apiKey,
address: selectedAccount.address,
type: 'reputation',
theme: 'dark',
showCategories: true
});
widgetRef.current.mount(widgetContainerRef.current);
}
return () => {
widgetRef.current?.destroy();
widgetRef.current = undefined;
};
}, [selectedAccount, apiKey]);
// Disconnect
const disconnect = useCallback(() => {
unsubscribeRef.current?.();
unsubscribeRef.current = null;
widgetRef.current?.destroy();
widgetRef.current = undefined;
setWallet(null);
setAccounts([]);
setSelectedAccount(null);
setScores(null);
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
unsubscribeRef.current?.();
widgetRef.current?.destroy();
};
}, []);
// Not installed
if (!isInstalled) {
return (
<div className="subwallet-connect">
<p>SubWallet is not installed.</p>
<a
href="https://subwallet.app/download"
target="_blank"
rel="noopener noreferrer"
className="install-link"
>
Install SubWallet
</a>
</div>
);
}
// Not connected
if (!wallet) {
return (
<div className="subwallet-connect">
<button
onClick={connectWallet}
disabled={isConnecting}
className="connect-button"
>
{isConnecting ? 'Connecting...' : 'Connect SubWallet'}
</button>
{error && <p className="error">{error}</p>}
</div>
);
}
// Connected
return (
<div className="subwallet-dotpassport">
<div className="account-selector">
<label>Account:</label>
<select
value={selectedAccount?.address}
onChange={(e) => {
const account = accounts.find(a => a.address === e.target.value);
setSelectedAccount(account || null);
}}
>
{accounts.map(account => (
<option key={account.address} value={account.address}>
{account.name || 'Account'} ({account.address.slice(0, 8)}...{account.address.slice(-6)})
</option>
))}
</select>
<button onClick={disconnect} className="disconnect-button">
Disconnect
</button>
</div>
{scores && (
<div className="scores-summary">
<h3>Reputation Score: {scores.totalScore}</h3>
</div>
)}
<div ref={widgetContainerRef} className="widget-container" />
{error && <p className="error">{error}</p>}
</div>
);
}
// Usage:
// <SubWalletDotPassport apiKey="your_api_key" />