use leptos::*; use leptos_router::use_params_map; use leptos_icons::*; use server_fn::error::NoCustomError; use crate::components::dashboard_row::DashboardRow; use crate::components::dashboard_tile::DashboardTile; use crate::components::song_list::*; use crate::components::loading::*; use crate::components::error::*; use crate::api::profile::*; use crate::models::User; use crate::users::get_user_by_id; use crate::util::state::GlobalState; /// Duration in seconds backwards from now to aggregate history data for const HISTORY_SECS: i64 = 60 * 60 * 24 * 30; const HISTORY_MESSAGE: &str = "Last Month"; /// How many top songs to show const TOP_SONGS_COUNT: i64 = 10; /// How many recent songs to show const RECENT_SONGS_COUNT: i64 = 5; /// How many recent artists to show const TOP_ARTISTS_COUNT: i64 = 10; /// Profile page /// Shows the current user's profile if no id is specified, or a user's profile if an id is specified in the path #[component] pub fn Profile() -> impl IntoView { let params = use_params_map(); view! {
{move || params.with(|params| { match params.get("id").map(|id| id.parse::()) { None => { // No id specified, show the current user's profile view! { }.into_view() }, Some(Ok(id)) => { // Id specified, get the user and show their profile view! { }.into_view() }, Some(Err(e)) => { // Invalid id, return an error view! { title="Invalid User ID" error=e.to_string() /> }.into_view() } } })}
} } /// Show the logged in user's profile #[component] fn OwnProfile() -> impl IntoView { view! { } > {move || GlobalState::logged_in_user().get().map(|user| { match user { Some(user) => { let user_id = user.id.unwrap(); view! { }.into_view() }, None => view! { title="Not Logged In" message="You must be logged in to view your profile" /> }.into_view(), } })} } } /// Show a user's profile by ID #[component] fn UserIdProfile(#[prop(into)] id: MaybeSignal) -> impl IntoView { let user_info = create_resource(move || id.get(), move |id| { get_user_by_id(id) }); // Show the details if the user is found let show_details = create_rw_signal(false); view!{ } > {move || user_info.get().map(|user| { match user { Ok(Some(user)) => { show_details.set(true); view! { }.into_view() }, Ok(None) => { show_details.set(false); view! { title="User Not Found" message=format!("User with ID {} not found", id.get()) /> }.into_view() }, Err(error) => { show_details.set(false); view! { title="Error Getting User" error /> }.into_view() } } })} } } /// Show a profile for a User object #[component] fn UserProfile(user: User) -> impl IntoView { let user_id = user.id.unwrap(); let profile_image_path = format!("/assets/images/profile/{}.webp", user_id); view! {

{user.username}

{user.email} { user.created_at.map(|created_at| { format!(" • Joined {}", created_at.format("%B %Y")) }) } { if user.admin { " • Admin" } else { "" } }

} } /// Show a list of top songs for a user #[component] fn TopSongs(#[prop(into)] user_id: MaybeSignal) -> impl IntoView { let top_songs = create_resource(move || user_id.get(), |user_id| async move { use chrono::{Local, Duration}; let now = Local::now(); let start = now - Duration::seconds(HISTORY_SECS); let top_songs = top_songs(user_id, start.naive_utc(), now.naive_utc(), Some(TOP_SONGS_COUNT)).await; top_songs.map(|top_songs| { top_songs.into_iter().map(|(plays, song)| { let plays = if plays == 1 { format!("{} Play", plays) } else { format!("{} Plays", plays) }; (song, plays) }).collect::>() }) }); view! {

{format!("Top Songs {}", HISTORY_MESSAGE)}

} > {e.to_string()}

}) .collect_view() } } > {move || top_songs.get().map(|top_songs| { top_songs.map(|top_songs| { view! { } }) }) }
} } /// Show a list of recently played songs for a user #[component] fn RecentSongs(#[prop(into)] user_id: MaybeSignal) -> impl IntoView { let recent_songs = create_resource(move || user_id.get(), |user_id| async move { let recent_songs = recent_songs(user_id, Some(RECENT_SONGS_COUNT)).await; recent_songs.map(|recent_songs| { recent_songs.into_iter().map(|(_date, song)| { song }).collect::>() }) }); view! {

"Recently Played"

} > {e.to_string()}

}) .collect_view() } } > {move || recent_songs.get().map(|recent_songs| { recent_songs.map(|recent_songs| { view! { } }) }) }
} } /// Show a list of top artists for a user #[component] fn TopArtists(#[prop(into)] user_id: MaybeSignal) -> impl IntoView { let top_artists = create_resource(move || user_id.get(), |user_id| async move { use chrono::{Local, Duration}; let now = Local::now(); let start = now - Duration::seconds(HISTORY_SECS); let top_artists = top_artists(user_id, start.naive_utc(), now.naive_utc(), Some(TOP_ARTISTS_COUNT)).await; top_artists.map(|top_artists| { top_artists.into_iter().map(|(_plays, artist)| { artist }).collect::>() }) }); view! { {format!("Top Artists {}", HISTORY_MESSAGE)} } > {format!("Top Artists {}", HISTORY_MESSAGE)} {move || errors.get() .into_iter() .map(|(_, e)| view! {

{e.to_string()}

}) .collect_view() } } > {move || top_artists.get().map(|top_artists| { top_artists.map(|top_artists| { let tiles = top_artists.into_iter().map(|artist| { Box::new(artist) as Box }).collect::>(); DashboardRow::new(format!("Top Artists {}", HISTORY_MESSAGE), tiles) }) }) }
} }