mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-19 11:15:49 +00:00
clean up known amendment page. add live tracking table
This commit is contained in:
@@ -202,7 +202,10 @@ export function TxExample(props: {
|
||||
}
|
||||
|
||||
function shieldsIoEscape(s: string) {
|
||||
return s.trim().replaceAll('-', '--').replaceAll('_', '__')
|
||||
return s.trim()
|
||||
.replace(/-/g, '--')
|
||||
.replace(/_/g, '__')
|
||||
.replace(/%/g, '%25')
|
||||
}
|
||||
|
||||
export function NotEnabled() {
|
||||
@@ -212,3 +215,147 @@ export function NotEnabled() {
|
||||
<span className="status not_enabled" title={translate("This feature is not currently enabled on the production XRP Ledger.")}><i className="fa fa-flask"></i></span>
|
||||
)
|
||||
}
|
||||
|
||||
// Generate amendments table with live mainnet data
|
||||
export function AmendmentsTable() {
|
||||
const [amendments, setAmendments] = React.useState<any[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchAmendments = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(`https://vhs.prod.ripplex.io/v1/network/amendments/vote/main/`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Sort amendments table
|
||||
const sortedAmendments = data.amendments
|
||||
.sort((a: any, b: any) => {
|
||||
// Sort by status priority (lower number = higher priority)
|
||||
const getStatusPriority = (amendment: any) => {
|
||||
if (amendment.consensus) return 1; // Open for Voting
|
||||
if (amendment.tx_hash) return 2; // Enabled
|
||||
};
|
||||
|
||||
const priorityA = getStatusPriority(a);
|
||||
const priorityB = getStatusPriority(b);
|
||||
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityA - priorityB;
|
||||
}
|
||||
|
||||
// Sort by rippled_version (descending)
|
||||
if (a.rippled_version !== b.rippled_version) {
|
||||
return b.rippled_version.localeCompare(a.rippled_version, undefined, { numeric: true });
|
||||
}
|
||||
|
||||
// Finally sort by name (ascending)
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
setAmendments(sortedAmendments);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch amendments');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAmendments();
|
||||
}, []);
|
||||
|
||||
// Fancy schmancy loading icon
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: '2rem' }}>
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="sr-only">Loading amendments...</span>
|
||||
</div>
|
||||
<div style={{ marginTop: '1rem' }}>Loading amendments...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>Error loading amendments:</strong> {error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render amendment table
|
||||
return (
|
||||
<div className="md-table-wrapper">
|
||||
<table className="md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" data-label="Name">Name</th>
|
||||
<th align="left" data-label="Introduced">Introduced</th>
|
||||
<th align="left" data-label="Status">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{amendments.map((amendment) => (
|
||||
<tr key={amendment.id}>
|
||||
<td align="left">
|
||||
<Link to={`#${amendment.name.toLowerCase()}`}>{amendment.name}</Link>
|
||||
</td>
|
||||
<td align="left">{amendment.rippled_version}</td>
|
||||
<td align="left">
|
||||
<AmendmentBadge amendment={amendment} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AmendmentBadge(props: { amendment: any }) {
|
||||
const [status, setStatus] = React.useState<string>('Loading...');
|
||||
const [href, setHref] = React.useState<string | undefined>(undefined);
|
||||
const [color, setColor] = React.useState<string>('blue');
|
||||
|
||||
React.useEffect(() => {
|
||||
const amendment = props.amendment;
|
||||
|
||||
// Check if amendment is enabled (has tx_hash)
|
||||
if (amendment.tx_hash) {
|
||||
const enabledDate = new Date(amendment.date).toISOString().split('T')[0];
|
||||
setStatus(`Enabled: ${enabledDate}`);
|
||||
setColor('green');
|
||||
setHref(`https://livenet.xrpl.org/transactions/${amendment.tx_hash}`);
|
||||
}
|
||||
// Check if amendment is currently being voted on (has consensus field)
|
||||
else if (amendment.consensus) {
|
||||
setStatus(`Open for Voting: ${amendment.consensus}`);
|
||||
setColor('80d0e0');
|
||||
setHref(undefined); // No link for voting amendments
|
||||
}
|
||||
}, [props.amendment]);
|
||||
|
||||
// Split the status at the colon to create two-color badge
|
||||
const parts = status.split(':');
|
||||
const label = shieldsIoEscape(parts[0] || '');
|
||||
const message = shieldsIoEscape(parts.slice(1).join(':') || '');
|
||||
|
||||
const badgeUrl = `https://img.shields.io/badge/${label}-${message}-${color}`;
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link to={href} target="_blank">
|
||||
<img src={badgeUrl} alt={status} className="shield" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return <img src={badgeUrl} alt={status} className="shield" />;
|
||||
}
|
||||
|
||||
@@ -215,3 +215,9 @@ export const txExample: Schema & { tagName: string } = {
|
||||
render: 'TxExample',
|
||||
selfClosing: true
|
||||
}
|
||||
|
||||
export const amendmentsTable: Schema & { tagName: string } = {
|
||||
tagName: 'amendments-table',
|
||||
render: 'AmendmentsTable',
|
||||
selfClosing: true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user