Skip to content

Commit e87a3f1

Browse files
author
Hemulin
committed
Added Ancestry and Vouch views to Account overview page
1 parent 7676fd3 commit e87a3f1

4 files changed

Lines changed: 441 additions & 2 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { FC, useEffect, useState } from 'react';
2+
import clsx from 'clsx';
3+
import ReactECharts from 'echarts-for-react';
4+
5+
import useAptos from '../../../../modules/aptos/index';
6+
7+
interface Props {
8+
address: string;
9+
}
10+
11+
interface AncestryData {
12+
tree: string[];
13+
}
14+
15+
const Ancestry: FC<Props> = ({ address }) => {
16+
const [options, setOptions] = useState<any>();
17+
const [loading, setLoading] = useState(true);
18+
const aptos = useAptos();
19+
20+
useEffect(() => {
21+
const load = async () => {
22+
try {
23+
const resources = await aptos.getAccountResources(`0x${address}`);
24+
25+
// Find the ancestry resource
26+
const ancestryResource = resources.find(
27+
(resource) => resource.type === '0x1::ancestry::Ancestry'
28+
);
29+
30+
if (!ancestryResource) {
31+
console.warn('No ancestry data found for this account');
32+
setLoading(false);
33+
return;
34+
}
35+
36+
const ancestryData = ancestryResource.data as AncestryData;
37+
const ancestryTree = ancestryData.tree;
38+
39+
// Create the full ancestry chain including current account
40+
const fullAncestryChain = [...ancestryTree, address];
41+
42+
// Get base URL for account links
43+
const baseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`;
44+
45+
// Transform data for ECharts graph visualization
46+
const nodes = fullAncestryChain.map((addr, index) => ({
47+
name: `${addr.slice(0, 8)}...`,
48+
value: addr,
49+
x: index * 100, // Spread horizontally
50+
y: 0, // All nodes on same Y coordinate
51+
symbolSize: index === fullAncestryChain.length - 1 ? 12 : 8,
52+
itemStyle: {
53+
color: index === fullAncestryChain.length - 1 ? '#5A68FF' : '#9BAEF1',
54+
},
55+
label: {
56+
show: true,
57+
fontSize: index === fullAncestryChain.length - 1 ? 12 : 10,
58+
fontWeight: index === fullAncestryChain.length - 1 ? 'bold' : 'normal',
59+
},
60+
}));
61+
62+
const links = fullAncestryChain.slice(0, -1).map((_, index) => ({
63+
source: index,
64+
target: index + 1,
65+
lineStyle: {
66+
color: '#DAE1FA',
67+
width: 3,
68+
},
69+
}));
70+
71+
setOptions({
72+
title: {
73+
text: 'Account Ancestry',
74+
left: 'center',
75+
top: 20,
76+
textStyle: {
77+
fontSize: 16,
78+
fontWeight: 'bold',
79+
},
80+
},
81+
series: [
82+
{
83+
type: 'graph',
84+
layout: 'none', // Use fixed positions
85+
animation: false,
86+
data: nodes,
87+
links,
88+
roam: false,
89+
lineStyle: {
90+
color: '#DAE1FA',
91+
width: 2,
92+
curveness: 0,
93+
},
94+
label: {
95+
show: true,
96+
position: 'bottom',
97+
fontSize: 10,
98+
color: '#333',
99+
},
100+
emphasis: {
101+
focus: 'adjacency',
102+
lineStyle: {
103+
width: 4,
104+
},
105+
},
106+
},
107+
],
108+
tooltip: {
109+
formatter: (params: any) => {
110+
const isCurrentAccount = params.data.value === address;
111+
return `${isCurrentAccount ? 'Current Account' : 'Ancestor'}: ${params.data.value}${!isCurrentAccount ? '<br/>Click to view account' : ''}`;
112+
},
113+
},
114+
});
115+
} catch (error) {
116+
console.error('Failed to load ancestry data:', error);
117+
} finally {
118+
setLoading(false);
119+
}
120+
};
121+
122+
load();
123+
}, [address, aptos]);
124+
125+
// Handle click events on chart nodes
126+
const onChartClick = (params: any) => {
127+
// Only allow clicking on ancestor nodes, not the current account
128+
if (params.data && params.data.value && params.data.value !== address) {
129+
const baseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`;
130+
const accountUrl = `${baseUrl}/accounts/${params.data.value}`;
131+
window.open(accountUrl, '_blank');
132+
}
133+
};
134+
135+
if (loading) {
136+
return (
137+
<div className="w-full rounded-md shadow overflow-hidden h-[300px] ring-1 ring-black ring-opacity-5 bg-white">
138+
<div className="flex items-center justify-center h-full">
139+
<div className="text-gray-500">Loading ancestry...</div>
140+
</div>
141+
</div>
142+
);
143+
}
144+
145+
if (!options) {
146+
return (
147+
<div className="w-full rounded-md shadow overflow-hidden h-[300px] ring-1 ring-black ring-opacity-5 bg-white">
148+
<div className="flex items-center justify-center h-full">
149+
<div className="text-gray-500">No ancestry data available</div>
150+
</div>
151+
</div>
152+
);
153+
}
154+
155+
return (
156+
<div
157+
className={clsx(
158+
'w-full rounded-md shadow overflow-hidden h-[300px]',
159+
'ring-1 ring-black ring-opacity-5',
160+
'bg-white',
161+
)}
162+
>
163+
<ReactECharts
164+
option={options}
165+
style={{ height: '100%', width: '100%' }}
166+
onEvents={{
167+
click: onChartClick
168+
}}
169+
/>
170+
</div>
171+
);
172+
};
173+
174+
export default Ancestry;

web-app/src/modules/core/routes/Account/HistoricalBalance.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const HistoricalBalance: FC<Props> = ({ address }) => {
2828
url: apiUrl,
2929
});
3030

31-
debugger;
3231
const historicalBalance = res.data.balance.map((it, index) => [
3332
res.data.timestamp[index] * 1e3,
3433
it / 1e6,
@@ -45,7 +44,16 @@ const HistoricalBalance: FC<Props> = ({ address }) => {
4544
setOptions({
4645
animation: false,
4746
color: ['#DAE1FA', '#5A68FF', '#9BAEF1'],
48-
grid: { top: 28, right: 30, bottom: 80, left: 120 },
47+
title: {
48+
text: 'Balance over time',
49+
left: 'center',
50+
top: 20,
51+
textStyle: {
52+
fontSize: 16,
53+
fontWeight: 'bold',
54+
},
55+
},
56+
grid: { top: 60, right: 30, bottom: 80, left: 120 },
4957
xAxis: {
5058
type: 'time',
5159
},

web-app/src/modules/core/routes/Account/Overview/Overview.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Types } from 'aptos';
55
import useAptos from '../../../../aptos';
66
import HistoricalBalance from '../HistoricalBalance';
77
import Movements from '../Movements';
8+
import Ancestry from '../Ancestry';
9+
import Vouching from '../Vouching';
810

911
const Overview: FC = () => {
1012
const { accountAddress } = useParams();
@@ -29,6 +31,8 @@ const Overview: FC = () => {
2931
</div>
3032
<div className="col-span-12 md:col-span-5 space-y-6">
3133
<HistoricalBalance address={accountAddress!} />
34+
<Ancestry address={accountAddress!} />
35+
<Vouching address={accountAddress!} />
3236
</div>
3337
</div>
3438
</div>

0 commit comments

Comments
 (0)