lssa/explorer_service/src/pages/account_page.rs

223 lines
11 KiB
Rust

use std::str::FromStr as _;
use indexer_service_protocol::{Account, AccountId};
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
use crate::{api, components::TransactionPreview};
/// Account page component
#[component]
pub fn AccountPage() -> impl IntoView {
let params = use_params_map();
let (tx_offset, set_tx_offset) = signal(0u32);
let (all_transactions, set_all_transactions) = signal(Vec::new());
let (is_loading, set_is_loading) = signal(false);
let (has_more, set_has_more) = signal(true);
let tx_limit = 10u32;
// Parse account ID from URL params
let account_id = move || {
let account_id_str = params.read().get("id").unwrap_or_default();
AccountId::from_str(&account_id_str).ok()
};
// Load account data
let account_resource = Resource::new(account_id, |acc_id_opt| async move {
match acc_id_opt {
Some(acc_id) => api::get_account(acc_id).await,
None => Err(leptos::prelude::ServerFnError::ServerError(
"Invalid account ID".to_string(),
)),
}
});
// Load initial transactions
let transactions_resource = Resource::new(account_id, move |acc_id_opt| async move {
match acc_id_opt {
Some(acc_id) => api::get_transactions_by_account(acc_id, tx_limit, 0).await,
None => Err(leptos::prelude::ServerFnError::ServerError(
"Invalid account ID".to_string(),
)),
}
});
// Update all_transactions when initial load completes
Effect::new(move || {
if let Some(Ok(txs)) = transactions_resource.get() {
set_all_transactions.set(txs.clone());
set_has_more.set(txs.len() as u32 == tx_limit);
}
});
// Load more transactions handler
let load_more = move |_| {
let Some(acc_id) = account_id() else {
return;
};
set_is_loading.set(true);
let current_offset = tx_offset.get() + tx_limit;
set_tx_offset.set(current_offset);
leptos::task::spawn_local(async move {
match api::get_transactions_by_account(acc_id, tx_limit, current_offset).await {
Ok(new_txs) => {
let txs_count = new_txs.len() as u32;
set_all_transactions.update(|txs| txs.extend(new_txs));
set_has_more.set(txs_count == tx_limit);
}
Err(e) => {
log::error!("Failed to load more transactions: {}", e);
}
}
set_is_loading.set(false);
});
};
view! {
<div class="account-page">
<Suspense fallback=move || view! { <div class="loading">"Loading account..."</div> }>
{move || {
account_resource
.get()
.map(|result| match result {
Ok(acc) => {
let Account {
program_owner,
balance,
data,
nonce,
} = acc;
let acc_id = account_id().expect("Account ID should be set");
let account_id_str = acc_id.to_string();
let program_id = program_owner.to_string();
let balance_str = balance.to_string();
let nonce_str = nonce.to_string();
let data_len = data.0.len();
view! {
<div class="account-detail">
<div class="page-header">
<h1>"Account"</h1>
</div>
<div class="account-info">
<h2>"Account Information"</h2>
<div class="info-grid">
<div class="info-row">
<span class="info-label">"Account ID:"</span>
<span class="info-value hash">{account_id_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Balance:"</span>
<span class="info-value">{balance_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Program Owner:"</span>
<span class="info-value hash">{program_id}</span>
</div>
<div class="info-row">
<span class="info-label">"Nonce:"</span>
<span class="info-value">{nonce_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Data:"</span>
<span class="info-value">{format!("{} bytes", data_len)}</span>
</div>
</div>
</div>
<div class="account-transactions">
<h2>"Transactions"</h2>
<Suspense fallback=move || {
view! { <div class="loading">"Loading transactions..."</div> }
}>
{move || {
transactions_resource
.get()
.map(|result| match result {
Ok(_) => {
let txs = all_transactions.get();
if txs.is_empty() {
view! {
<div class="no-transactions">
"No transactions found"
</div>
}
.into_any()
} else {
view! {
<div>
<div class="transactions-list">
{txs
.into_iter()
.map(|tx| {
view! { <TransactionPreview transaction=tx /> }
})
.collect::<Vec<_>>()}
</div>
{move || {
if has_more.get() {
view! {
<button
class="load-more-button"
on:click=load_more
disabled=move || is_loading.get()
>
{move || {
if is_loading.get() {
"Loading..."
} else {
"Load More"
}
}}
</button>
}
.into_any()
} else {
().into_any()
}
}}
</div>
}
.into_any()
}
}
Err(e) => {
view! {
<div class="error">
{format!("Failed to load transactions: {}", e)}
</div>
}
.into_any()
}
})
}}
</Suspense>
</div>
</div>
}
.into_any()
}
Err(e) => {
view! {
<div class="error-page">
<h1>"Error"</h1>
<p>{format!("Failed to load account: {}", e)}</p>
</div>
}
.into_any()
}
})
}}
</Suspense>
</div>
}
}