Skip to content

Commit 336fcbb

Browse files
UltraDAGcomClaude Opus 4.6 (1M context)
andcommitted
Fix profile system: voting check, stream response, resolve logic
- useProfile: fetch individual proposals to check voting (GET /proposals doesn't include voters, only GET /proposal/{id} does) - useProfile: handle streams endpoint response shape {streams:[...]} not raw array - useProfile: setLoading(false) when address is undefined (prevents infinite spinner on /profile/me with no wallet) - ProfilePage: use isValidAddress() before normalizeAddress() to properly detect names vs addresses - EditProfileModal: mark as "Coming Soon" since no UpdateProfile convenience endpoint exists yet (was silently failing with postTxSubmit) - Remove unused imports for strict build Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3dbb88e commit 336fcbb

5 files changed

Lines changed: 111 additions & 151 deletions

File tree

dashboard/src/components/profile/EditProfileModal.tsx

Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { useState } from 'react';
2-
import { postTxSubmit, getNodeUrl } from '../../lib/api';
3-
import { getPasskeyWallet } from '../../lib/passkey-wallet';
42
import { useToast } from '../../hooks/useToast';
53
import type { Wallet } from '../../lib/keystore';
64

@@ -28,68 +26,25 @@ const labelStyle: React.CSSProperties = {
2826
display: 'block', marginBottom: 4, fontWeight: 600,
2927
};
3028

31-
export function EditProfileModal({ name, wallet, currentBio, currentWebsite, currentGithub, currentTwitter, currentExternalAddresses, onClose, onSuccess }: EditProfileModalProps) {
29+
export function EditProfileModal({ name, wallet: _wallet, currentBio, currentWebsite, currentGithub, currentTwitter, currentExternalAddresses, onClose, onSuccess: _onSuccess }: EditProfileModalProps) {
3230
const [bio, setBio] = useState(currentBio ?? '');
3331
const [website, setWebsite] = useState(currentWebsite ?? '');
3432
const [github, setGithub] = useState(currentGithub ?? '');
3533
const [twitter, setTwitter] = useState(currentTwitter ?? '');
3634
const [ethAddr, setEthAddr] = useState(currentExternalAddresses.find(([k]) => k === 'eth')?.[1] ?? '');
3735
const [btcAddr, setBtcAddr] = useState(currentExternalAddresses.find(([k]) => k === 'btc')?.[1] ?? '');
38-
const [loading, setLoading] = useState(false);
36+
const [loading] = useState(false);
3937
const [error, setError] = useState('');
4038
const { toast } = useToast();
4139

42-
const isPasskey = !!getPasskeyWallet() && !wallet.secret_key;
40+
// TODO: Profile editing requires either a testnet convenience endpoint for
41+
// UpdateProfileTx or client-side ed25519 signing. Neither is available yet.
42+
// For now, show the form as read-only preview of what will be saved.
43+
const canSave = false;
4344

4445
const handleSave = async () => {
45-
if (isPasskey) {
46-
setError('SmartOp UpdateProfile not yet supported for passkey wallets. Use a legacy wallet.');
47-
return;
48-
}
49-
setError('');
50-
setLoading(true);
51-
52-
try {
53-
// Build metadata entries
54-
const metadata: Array<[string, string]> = [];
55-
if (bio.trim()) metadata.push(['bio', bio.trim()]);
56-
if (website.trim()) metadata.push(['website', website.trim()]);
57-
if (github.trim()) metadata.push(['github', github.trim()]);
58-
if (twitter.trim()) metadata.push(['twitter', twitter.trim()]);
59-
60-
// Build external addresses
61-
const externalAddresses: Array<[string, string]> = [];
62-
if (ethAddr.trim()) externalAddresses.push(['eth', ethAddr.trim()]);
63-
if (btcAddr.trim()) externalAddresses.push(['btc', btcAddr.trim()]);
64-
65-
// Get nonce
66-
const balRes = await fetch(`${getNodeUrl()}/balance/${wallet.address}`, { signal: AbortSignal.timeout(5000) });
67-
const balData = await balRes.json();
68-
const nonce = balData.nonce ?? 0;
69-
const fee = 10000; // MIN_FEE
70-
71-
// Build and sign UpdateProfileTx
72-
// The /profile/update endpoint doesn't exist, so we use /tx with the update_profile type
73-
const body = {
74-
secret_key: wallet.secret_key,
75-
name,
76-
external_addresses: externalAddresses,
77-
metadata,
78-
fee,
79-
nonce,
80-
};
81-
82-
await postTxSubmit({ type: 'update_profile', ...body });
83-
toast('Profile updated!', 'success');
84-
onSuccess();
85-
onClose();
86-
} catch (e: unknown) {
87-
const msg = e instanceof Error ? e.message : 'Failed to update profile';
88-
setError(msg);
89-
toast(msg, 'error');
90-
} finally {
91-
setLoading(false);
92-
}
46+
setError('Profile editing is coming soon. The UpdateProfile transaction endpoint is being added to the next release.');
47+
toast('Profile editing coming soon', 'info');
9348
};
9449

9550
return (
@@ -105,11 +60,9 @@ export function EditProfileModal({ name, wallet, currentBio, currentWebsite, cur
10560
<button onClick={onClose} style={{ background: 'none', border: 'none', color: 'var(--dag-text-faint)', fontSize: 18, cursor: 'pointer' }}></button>
10661
</div>
10762

108-
{isPasskey && (
109-
<div style={{ marginBottom: 14, padding: '10px 12px', background: 'rgba(255,184,0,0.06)', border: '1px solid rgba(255,184,0,0.15)', borderRadius: 8, fontSize: 11, color: '#FFB800' }}>
110-
Profile editing is not yet available for passkey wallets. SmartOp UpdateProfile support coming soon.
111-
</div>
112-
)}
63+
<div style={{ marginBottom: 14, padding: '10px 12px', background: 'rgba(0,224,196,0.06)', border: '1px solid rgba(0,224,196,0.15)', borderRadius: 8, fontSize: 11, color: '#00E0C4' }}>
64+
Preview your profile changes below. Saving will be enabled in the next release when the UpdateProfile endpoint is live.
65+
</div>
11366

11467
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
11568
<div>
@@ -162,13 +115,13 @@ export function EditProfileModal({ name, wallet, currentBio, currentWebsite, cur
162115
</div>
163116
)}
164117

165-
<button onClick={handleSave} disabled={loading || isPasskey} style={{
118+
<button onClick={handleSave} disabled={!canSave || loading} style={{
166119
width: '100%', padding: '12px 0', borderRadius: 10, border: 'none', marginTop: 16,
167-
background: loading || isPasskey ? 'var(--dag-input-bg)' : 'linear-gradient(135deg, #00E0C4, #0066FF)',
168-
color: loading || isPasskey ? 'var(--dag-text-faint)' : '#fff',
169-
fontSize: 13, fontWeight: 700, cursor: loading || isPasskey ? 'default' : 'pointer',
120+
background: !canSave || loading ? 'var(--dag-input-bg)' : 'linear-gradient(135deg, #00E0C4, #0066FF)',
121+
color: !canSave || loading ? 'var(--dag-text-faint)' : '#fff',
122+
fontSize: 13, fontWeight: 700, cursor: !canSave || loading ? 'default' : 'pointer',
170123
}}>
171-
{loading ? 'Saving...' : 'Save Profile'}
124+
{loading ? 'Saving...' : 'Save Profile (Coming Soon)'}
172125
</button>
173126
</div>
174127
</div>

dashboard/src/hooks/useProfile.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect, useCallback } from 'react';
2-
import { getBalance, getStake, getDelegation, getSmartAccount, getNameInfo, getStreamsSender, getStreamsRecipient, getProposals } from '../lib/api';
2+
import { getBalance, getStake, getDelegation, getSmartAccount, getNameInfo, getStreamsSender, getStreamsRecipient, getProposals, getProposal } from '../lib/api';
33
import { computeBadges, type Badge, type BadgeInput } from '../lib/badges';
44

55
export interface ProfileData {
@@ -39,7 +39,7 @@ export function useProfile(address: string | undefined) {
3939
const [error, setError] = useState<string | null>(null);
4040

4141
const fetchProfile = useCallback(async () => {
42-
if (!address) return;
42+
if (!address) { setLoading(false); return; }
4343
setLoading(true);
4444
setError(null);
4545

@@ -59,8 +59,10 @@ export function useProfile(address: string | undefined) {
5959
const stake = stakeRes.status === 'fulfilled' ? stakeRes.value : null;
6060
const deleg = delegRes.status === 'fulfilled' ? delegRes.value : null;
6161
const sa = saRes.status === 'fulfilled' ? saRes.value : null;
62-
const streamsOut = streamsOutRes.status === 'fulfilled' ? (Array.isArray(streamsOutRes.value) ? streamsOutRes.value : []) : [];
63-
const streamsIn = streamsInRes.status === 'fulfilled' ? (Array.isArray(streamsInRes.value) ? streamsInRes.value : []) : [];
62+
const streamsOutData = streamsOutRes.status === 'fulfilled' ? streamsOutRes.value : null;
63+
const streamsInData = streamsInRes.status === 'fulfilled' ? streamsInRes.value : null;
64+
const streamsOut = Array.isArray(streamsOutData?.streams) ? streamsOutData.streams : (Array.isArray(streamsOutData) ? streamsOutData : []);
65+
const streamsIn = Array.isArray(streamsInData?.streams) ? streamsInData.streams : (Array.isArray(streamsInData) ? streamsInData : []);
6466
const proposals = proposalsRes.status === 'fulfilled' ? (Array.isArray(proposalsRes.value) ? proposalsRes.value : []) : [];
6567

6668
if (!bal) { setError('Address not found'); setLoading(false); return; }
@@ -75,14 +77,18 @@ export function useProfile(address: string | undefined) {
7577
const metadata = profileMeta.metadata ?? [];
7678
const getMeta = (key: string) => metadata.find(([k]: [string, string]) => k === key)?.[1] ?? null;
7779

78-
// Count proposals this address voted on
80+
// Count proposals this address voted on (check up to 10 recent proposals)
7981
let votedCount = 0;
80-
for (const p of proposals) {
81-
if (p.voters && Array.isArray(p.voters)) {
82-
if (p.voters.some((v: Record<string, unknown>) => String(v.address) === address)) {
83-
votedCount++;
82+
const recentProposals = proposals.slice(0, 10);
83+
for (const p of recentProposals) {
84+
try {
85+
const detail = await getProposal(p.id);
86+
if (detail?.voters && Array.isArray(detail.voters)) {
87+
if (detail.voters.some((v: Record<string, unknown>) => String(v.address) === address)) {
88+
votedCount++;
89+
}
8490
}
85-
}
91+
} catch { /* skip */ }
8692
}
8793

8894
const data: ProfileData = {

dashboard/src/pages/ProfilePage.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useProfile } from '../hooks/useProfile';
44
import { useKeystore } from '../hooks/useKeystore';
55
import { useIsMobile } from '../hooks/useIsMobile';
66
import { getPasskeyWallet } from '../lib/passkey-wallet';
7-
import { getBalance, normalizeAddress } from '../lib/api';
7+
import { getBalance, normalizeAddress, isValidAddress } from '../lib/api';
88
import { UltraIdCard } from '../components/profile/UltraIdCard';
99
import { EditProfileModal } from '../components/profile/EditProfileModal';
1010
import { ProfileActivity } from '../components/profile/ProfileActivity';
@@ -36,10 +36,11 @@ export function ProfilePage() {
3636
input = input.replace(/^@/, '');
3737

3838
// Try as address first (hex or bech32)
39-
try {
40-
const normalized = normalizeAddress(input);
41-
if (normalized) { setResolvedAddress(normalized); setResolving(false); return; }
42-
} catch { /* not an address, try as name */ }
39+
if (isValidAddress(input)) {
40+
setResolvedAddress(normalizeAddress(input));
41+
setResolving(false);
42+
return;
43+
}
4344

4445
// Resolve as name via /balance/ endpoint (accepts names)
4546
try {

0 commit comments

Comments
 (0)