Work on the user list page

This commit is contained in:
Savanni D'Gerinel 2025-04-13 00:05:09 -04:00
parent e5a0c85e18
commit 0234a880cd
8 changed files with 149 additions and 61 deletions

View File

@ -12,7 +12,7 @@ serde_json = { workspace = true }
visions-types = { path = "../types" }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
web-sys = { workspace = true }
web-sys = { workspace = true, features = ["Window"] }
yew = { workspace = true }
yew-router = { workspace = true }

View File

@ -229,3 +229,22 @@ body {
background-color: var(--button-background-dark);
color: var(--text-inverse);
}
.user-overview {
display: flex;
margin-top: var(--spacing-s);
padding: var(--spacing-m);
border-radius: var(--radius-m);
}
.user-overview:nth-child(odd) {
background-color: var(--highlight-background);
}
.user-overview__name {
width: 65%;
}
.button-bar {
justify-content: space-between;
}

View File

@ -2,7 +2,9 @@ use yew::{function_component, html, AttrValue, Callback, Html, MouseEvent, Prope
#[derive(Properties, PartialEq)]
pub struct ButtonProps {
#[prop_or(Callback::from(|_| ()))]
pub on_click: Callback<MouseEvent>,
pub children: Html
}

View File

@ -1,14 +1,17 @@
use yew::{function_component, html, Html, Properties};
use yew::{function_component, html, AttrValue, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct RowProps {
#[prop_or_default]
pub class: Option<AttrValue>,
pub children: Html
}
#[function_component]
pub fn Row(RowProps { children }: &RowProps) -> Html {
pub fn Row(RowProps { ref class, children }: &RowProps) -> Html {
let classes = format!("row {}", class.as_ref().map(|cn| cn.as_str().to_string()).unwrap_or("".to_string()));
html! {
<div class="row">
<div class={classes}>
{children.clone()}
</div>
}

View File

@ -1,7 +1,8 @@
use std::rc::Rc;
use gloo_console::log;
use visions_types::{AuthResponse, SessionId, UserOverview};
use views::UserList;
use visions_types::{AuthResponse, SessionId};
use yew::prelude::*;
use yew_router::prelude::*;
@ -42,7 +43,6 @@ impl Reducible for AuthInfo {
type Action = AuthAction;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
// log!("reduce", action);
match action {
AuthAction::Auth(session_id) => Self {
session_id: Some(session_id.into()),
@ -53,59 +53,21 @@ impl Reducible for AuthInfo {
}
}
#[derive(Properties, PartialEq)]
struct LandingProps {
session_id: SessionId,
}
#[function_component]
fn Landing(LandingProps { session_id }: &LandingProps) -> Html {
let user_ref = use_state(|| vec![]);
{
let user_ref = user_ref.clone();
let session_id = session_id.clone();
use_effect(move || {
wasm_bindgen_futures::spawn_local(async move {
let client = Connection::new();
match client.list_users(&session_id).await {
Ok(users) => user_ref.set(users),
Err(ClientError::Unauthorized) => todo!(),
Err(ClientError::Err(status)) => {
log!("error: {:?}", status);
todo!()
}
}
})
});
}
html! {
<div>
{"Landing Page"}
{user_ref.iter().map(|overview| {
let overview = overview.clone();
html! { <UserOverviewComponent overview={overview} /> }}).collect::<Vec<Html>>()
}
</div>
}
}
#[derive(Properties, PartialEq)]
struct UserOverviewProps {
overview: UserOverview,
}
#[function_component]
fn UserOverviewComponent(UserOverviewProps { overview }: &UserOverviewProps) -> Html {
html! {
<div> { overview.name.clone() } </div>
}
}
#[function_component]
fn App() -> Html {
let auth_info = use_reducer(AuthInfo::default);
let auth_info = use_reducer(init_state);
/*
use_effect(|| match web_sys::window() {
Some(window) => match window.session_storage() {
Ok(Some(storage)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
_ => (),
},
None => (),
});
*/
let on_login = {
let auth_info = auth_info.clone();
@ -115,6 +77,7 @@ fn App() -> Html {
let client = Connection::new();
match client.auth(username, password).await {
Ok(AuthResponse::Success(session_id)) => {
save_session_id(session_id.clone());
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Ok(AuthResponse::PasswordReset(session_id)) => {
@ -128,7 +91,7 @@ fn App() -> Html {
};
match auth_info.session_id {
Some(ref session_id) => html! { <Landing session_id={session_id.clone()} /> },
Some(ref session_id) => html! { <UserList session_id={session_id.clone()} /> },
None => html! { <Login on_login={on_login.clone()} /> },
}
}
@ -140,6 +103,35 @@ fn switch(routes: Route) -> Html {
}
}
fn init_state() -> AuthInfo {
match web_sys::window() {
Some(window) => match window.session_storage() {
Ok(Some(storage)) => {
if let Ok(Some(session_id)) = storage.get_item("session_id") {
AuthInfo {
session_id: Some(session_id.into()),
}
} else {
AuthInfo::default()
}
}
_ => AuthInfo::default(),
},
None => AuthInfo::default(),
}
}
fn save_session_id(session_id: SessionId) {
log!("save_session_id", web_sys::window());
match web_sys::window() {
Some(window) => match window.session_storage() {
Ok(Some(storage)) => storage.set_item("session_id", session_id.as_str()).unwrap(),
_ => (),
},
None => (),
}
}
#[function_component]
fn AppWrapper() -> Html {
html! {

View File

@ -1,7 +1,5 @@
use gloo_console::log;
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::{function_component, html, use_state, Callback, Event, Html, Properties};
use yew::{function_component, html, use_state, Callback, Html, Properties};
use crate::components::{Button, TextEntry};

View File

@ -4,3 +4,6 @@ pub use design::Design;
mod login;
pub use login::Login;
mod user_list;
pub use user_list::UserList;

View File

@ -0,0 +1,71 @@
use gloo_console::log;
use visions_types::{AccountStatus, SessionId, UserOverview};
use yew::{function_component, html, use_effect, use_state, Html, Properties};
use crate::{client::{Client, ClientError, Connection}, components::*};
#[derive(Properties, PartialEq)]
pub struct UserListProps {
pub session_id: SessionId,
}
#[function_component]
pub fn UserList(UserListProps { session_id }: &UserListProps) -> Html {
let user_ref = use_state(|| vec![]);
{
let user_ref = user_ref.clone();
let session_id = session_id.clone();
use_effect(move || {
wasm_bindgen_futures::spawn_local(async move {
let client = Connection::new();
match client.list_users(&session_id).await {
Ok(users) => user_ref.set(users),
Err(ClientError::Unauthorized) => {
todo!()
}
Err(ClientError::Err(status)) => {
log!("error: {:?}", status);
todo!()
}
}
})
});
}
html! {
<>
<div>
{user_ref.iter().map(|overview| {
let overview = overview.clone();
html! { <UserOverviewComponent overview={overview} /> }}).collect::<Vec<Html>>()
}
</div>
<Row class="button-bar">
<Button>{"+ Add User"}</Button>
<Button>{"Go to game"}</Button>
</Row>
</>
}
}
#[derive(Properties, PartialEq)]
struct UserOverviewProps {
overview: UserOverview,
}
#[function_component]
fn UserOverviewComponent(UserOverviewProps { overview }: &UserOverviewProps) -> Html {
let account_status = match overview.status {
AccountStatus::Ok => "",
AccountStatus::PasswordReset(_) => "Password Reset",
AccountStatus::Locked => "Locked",
};
html! {
<div class="user-overview">
<div class="user-overview__name">{overview.name.clone()}</div>
<div class="user-overview__status">{account_status}</div>
</div>
}
}