use leptos::prelude::*; use leptos_router::{ NavigateOptions, hooks::{use_navigate, use_query_map}, }; use web_sys::SubmitEvent; use crate::{ api::{self, SearchResults}, components::{AccountPreview, BlockPreview, TransactionPreview}, }; const RECENT_BLOCKS_LIMIT: u64 = 10; /// Main page component #[component] pub fn MainPage() -> impl IntoView { let query_map = use_query_map(); let navigate = use_navigate(); // Read search query from URL parameter let url_query = move || query_map.read().get("q").unwrap_or_default(); let (search_query, set_search_query) = signal(url_query()); // Sync search input with URL parameter Effect::new(move || { set_search_query.set(url_query()); }); // Search results resource based on URL query parameter let search_resource = Resource::new(url_query, |query| async move { if query.is_empty() { return None; } match api::search(query).await { Ok(result) => Some(result), Err(e) => { log::error!("Search error: {e}"); None } } }); // Pagination state for blocks let (all_blocks, set_all_blocks) = signal(Vec::new()); let (is_loading_blocks, set_is_loading_blocks) = signal(false); let (has_more_blocks, set_has_more_blocks) = signal(true); let (oldest_loaded_block_id, set_oldest_loaded_block_id) = signal(None::); // Load recent blocks on mount let recent_blocks_resource = Resource::new( || (), |()| async { api::get_blocks(None, RECENT_BLOCKS_LIMIT).await }, ); // Update all_blocks when initial load completes Effect::new(move || { if let Some(Ok(blocks)) = recent_blocks_resource.get() { let oldest_id = blocks.last().map(|b| b.header.block_id); set_all_blocks.set(blocks.clone()); set_oldest_loaded_block_id.set(oldest_id); set_has_more_blocks.set( u64::try_from(blocks.len()).expect("usize should fit in u64") == RECENT_BLOCKS_LIMIT && oldest_id.unwrap_or(0) > 1, ); } }); // Load more blocks handler let load_more_blocks = move |_| { let before_id = oldest_loaded_block_id.get(); if before_id.is_none() { return; } set_is_loading_blocks.set(true); leptos::task::spawn_local(async move { match api::get_blocks(before_id, RECENT_BLOCKS_LIMIT).await { Ok(new_blocks) => { let blocks_count = u64::try_from(new_blocks.len()).expect("usize should fit in u64"); let new_oldest_id = new_blocks.last().map(|b| b.header.block_id); set_all_blocks.update(|blocks| blocks.extend(new_blocks)); set_oldest_loaded_block_id.set(new_oldest_id); set_has_more_blocks .set(blocks_count == RECENT_BLOCKS_LIMIT && new_oldest_id.unwrap_or(0) > 1); } Err(e) => { log::error!("Failed to load more blocks: {e}"); } } set_is_loading_blocks.set(false); }); }; // Handle search - update URL parameter let on_search = move |ev: SubmitEvent| { ev.prevent_default(); let query = search_query.get(); if query.is_empty() { navigate("?", NavigateOptions::default()); return; } navigate( &format!("?q={}", urlencoding::encode(&query)), NavigateOptions::default(), ); }; view! {
"Searching..."
}> {move || { search_resource .get() .and_then(|opt_results| opt_results) .map(|results| { let SearchResults { blocks, transactions, accounts, } = results; let has_results = !blocks.is_empty() || !transactions.is_empty() || !accounts.is_empty(); view! {

"Search Results"

{if has_results { view! {
{if blocks.is_empty() { ().into_any() } else { view! {

"Blocks"

{blocks .into_iter() .map(|block| { view! { } }) .collect::>()}
} .into_any() }} {if transactions.is_empty() { ().into_any() } else { view! {

"Transactions"

{transactions .into_iter() .map(|tx| { view! { } }) .collect::>()}
} .into_any() }} {if accounts.is_empty() { ().into_any() } else { view! {

"Accounts"

{accounts .into_iter() .map(|(id, account)| { view! { } }) .collect::>()}
} .into_any() }}
} .into_any() } else { view! {
"No results found"
} .into_any() }}
} .into_any() }) }}

"Recent Blocks"

"Loading blocks..."
}> {move || { recent_blocks_resource .get() .map(|result| match result { Ok(_) => { let blocks = all_blocks.get(); if blocks.is_empty() { view! {
"No blocks found"
} .into_any() } else { view! {
{blocks .into_iter() .map(|block| view! { }) .collect::>()}
{move || { if has_more_blocks.get() { view! { } .into_any() } else { ().into_any() } }}
} .into_any() } } Err(e) => { view! {
{format!("Error: {e}")}
} .into_any() } }) }} } }