Compare commits

...

2 Commits

12 changed files with 228 additions and 112 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

@ -183,6 +183,7 @@ body {
color: var(--text-light);
}
/*
.label__edit-button {
margin-left: var(--spacing-l);
padding: var(--spacing-s);
@ -192,6 +193,11 @@ body {
background-color: var(--button-background-dark);
color: var(--text-inverse);
}
*/
.label > .cta {
margin-left: var(--spacing-l);
}
.row {
margin: var(--spacing-m);
@ -215,3 +221,30 @@ body {
color: var(--text-light);
}
.cta {
margin: 0;
padding: var(--spacing-m);
border: var(--border-light);
border-radius: var(--radius-l);
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

@ -0,0 +1,16 @@
use yew::{function_component, html, AttrValue, Callback, Html, MouseEvent, Properties};
#[derive(Properties, PartialEq)]
pub struct ButtonProps {
#[prop_or(Callback::from(|_| ()))]
pub on_click: Callback<MouseEvent>,
pub children: Html
}
#[function_component]
pub fn Button(ButtonProps{ on_click, children }: &ButtonProps) -> Html {
html! {
<button class="cta" onclick={on_click}>{children.clone()}</button>
}
}

View File

@ -1,5 +1,7 @@
use yew::{function_component, html, AttrValue, Html, Properties};
use crate::components::Button;
#[derive(Properties, PartialEq)]
pub struct LabelProps {
#[prop_or(AttrValue::from(""))]
@ -19,7 +21,7 @@ pub fn Label(LabelProps { text, placeholder, editable }: &LabelProps) -> Html {
};
let edit_button = if *editable {
html! { <span class="label__edit-button">{"edit"}</span> }
html! { <Button on_click={|_| ()}>{"Edit"}</Button> }
} else { html! { } };
html! {

View File

@ -1,3 +1,6 @@
mod button;
pub use button::Button;
mod card;
pub use card::Card;

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,6 +1,8 @@
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::{function_component, html, use_node_ref, use_state, AttrValue, Callback, Event, Html, Properties};
use yew::{
function_component, html, use_node_ref, use_state, AttrValue, Callback, Event, Html, Properties,
};
#[derive(Properties, PartialEq)]
pub struct TextEntryProps {
@ -11,14 +13,26 @@ pub struct TextEntryProps {
#[prop_or(None)]
pub on_changed: Option<Callback<String>>,
#[prop_or(false)]
pub sensitive: bool,
}
#[function_component]
pub fn TextEntry(TextEntryProps { value, placeholder, on_changed }: &TextEntryProps) -> Html {
pub fn TextEntry(
TextEntryProps {
value,
placeholder,
on_changed,
sensitive,
}: &TextEntryProps,
) -> Html {
let on_changed_ = {
let on_changed = on_changed.clone();
Callback::from(move |event: Event| {
let input = event.target().and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Some(ref on_changed) = on_changed {
on_changed.emit(input.value());
@ -27,7 +41,13 @@ pub fn TextEntry(TextEntryProps { value, placeholder, on_changed }: &TextEntryPr
})
};
html! {
<input class="text-entry" type="text" placeholder={placeholder} onchange={on_changed_} value={value} />
if *sensitive {
html! {
<input class="text-entry" type="password" placeholder={placeholder} onchange={on_changed_} value={value} />
}
} else {
html! {
<input class="text-entry" type="text" placeholder={placeholder} onchange={on_changed_} value={value} />
}
}
}

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,20 +77,21 @@ 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)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Err(ClientError::Unauthorized) => todo!(),
Err(ClientError::Err(status)) => todo!(),
Err(ClientError::Err(_status)) => todo!(),
};
})
})
};
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,3 +1,4 @@
use gloo_console::log;
use yew::{function_component, html, Html, Properties};
use crate::components::*;
@ -53,6 +54,14 @@ pub fn Design(DesignProps { }: &DesignProps) -> Html {
<TextEntry value="vakarian" placeholder="username" />
</Row>
<Row>
<Button on_click={|_| {
log!("on_click");
}}>
{"Click Me"}
</Button>
</Row>
<Row>
<Card title="Card Title">
<p>{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}</p>

View File

@ -1,8 +1,7 @@
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::{function_component, html, use_state, Callback, Event, Html, Properties};
use gloo_console::log;
use yew::{function_component, html, use_state, Callback, Html, Properties};
use crate::components::TextEntry;
use crate::components::{Button, TextEntry};
#[derive(Properties, PartialEq)]
pub struct LoginProps {
@ -23,59 +22,24 @@ pub fn Login(LoginProps { on_login }: &LoginProps) -> Html {
let on_username_changed = {
let username = username.clone();
Callback::from(move |text: String| username.set(text))
};
let on_password_changed = {
let username = username.clone();
Callback::from(move |text: String| username.set(text))
};
/*
let on_username_changed = {
let username = username.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
username.set(input.value());
}
Callback::from(move |text: String| {
log!("on_username_changed", text.clone());
username.set(text);
})
};
let on_password_changed = {
let password = password.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
password.set(input.value());
}
})
let password = password .clone();
Callback::from(move |text: String| password .set(text))
};
*/
/*
html! {
<div class="login-form">
<div class="card">
<h1>{"Welcome to Visions VTT"}</h1>
<input type="text" name="username" placeholder="username" onchange={on_username_changed} />
<input type="password" name="password" placeholder="password" onchange={on_password_changed} />
<button onclick={on_click}>{"Login"}</button>
</div>
</div>
}
*/
html! {
<div class="login-form">
<div class="card">
<h1>{"Welcome to Visions VTT"}</h1>
<TextEntry placeholder="username" on_changed={on_username_changed} />
<TextEntry placeholder="password" on_changed={on_password_changed} />
<button onclick={on_click}>{"Login"}</button>
<TextEntry placeholder="username" value={(*username).clone()} on_changed={on_username_changed} />
<TextEntry placeholder="password" value={(*password).clone()} on_changed={on_password_changed} sensitive={true} />
<Button on_click={on_click}>{"Login"}</Button>
</div>
</div>
}

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>
}
}