diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-03-19 07:45:14 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-03-19 07:45:14 +0100 |
| commit | f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb (patch) | |
| tree | 5f28fbca03d83921b568f7cb1708374456d9ec42 /src/components | |
| parent | feat(treewide): Add further buttons (diff) | |
| download | web-client-f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb.zip | |
feat(treewide): Commit MVP
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/async_fetch.rs | 12 | ||||
| -rw-r--r-- | src/components/buy.rs | 1 | ||||
| -rw-r--r-- | src/components/catch_errors.rs | 40 | ||||
| -rw-r--r-- | src/components/checkbox_placeholder.rs | 54 | ||||
| -rw-r--r-- | src/components/container.rs | 52 | ||||
| -rw-r--r-- | src/components/inventory.rs | 4 | ||||
| -rw-r--r-- | src/components/login_wall.rs | 42 | ||||
| -rw-r--r-- | src/components/mod.rs | 7 | ||||
| -rw-r--r-- | src/components/product_overview.rs | 19 | ||||
| -rw-r--r-- | src/components/product_parent_overview.rs | 37 | ||||
| -rw-r--r-- | src/components/recipies.rs | 20 | ||||
| -rw-r--r-- | src/components/textarea_placeholder.rs | 60 | ||||
| -rw-r--r-- | src/components/unit_overview.rs | 37 |
13 files changed, 336 insertions, 49 deletions
diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs index 7bf44a0..43469a7 100644 --- a/src/components/async_fetch.rs +++ b/src/components/async_fetch.rs @@ -37,11 +37,13 @@ macro_rules! AsyncFetch { <Transition fallback=|| { view! { <p>"Loading..."</p> } }> - {move || Suspend::new(async move { - $resource - .await - .map($producer) - })} + { + Suspend::new(async move { + $resource + .await + .map($producer) + }) + } </Transition> } }}; diff --git a/src/components/buy.rs b/src/components/buy.rs index e69de29..8b13789 100644 --- a/src/components/buy.rs +++ b/src/components/buy.rs @@ -0,0 +1 @@ + diff --git a/src/components/catch_errors.rs b/src/components/catch_errors.rs new file mode 100644 index 0000000..d5a452d --- /dev/null +++ b/src/components/catch_errors.rs @@ -0,0 +1,40 @@ +use leptos::{ + IntoView, component, + error::ErrorBoundary, + prelude::{Children, ClassAttribute, CollectView, ElementChild, Get}, + view, +}; + +use crate::components::site_header::SiteHeader; + +#[component] +pub(crate) fn CatchErrors(children: Children) -> impl IntoView { + view! { + <ErrorBoundary fallback=|errors| { + view! { + <SiteHeader + logo=icondata_io::IoRoseSharp + back_location="/" + name="Errors occurred" + /> + + <h1>"Uh oh! Something went wrong!"</h1> + + <p>"Errors: "</p> + <ul class="flex flex-col gap-1"> + {move || { + errors + .get() + .into_iter() + .map(|(_, e)| { + view! { + <li class="bg-gray-200 rounded-lg m-2 p-1">{e.to_string()}</li> + } + }) + .collect_view() + }} + </ul> + } + }>{children()}</ErrorBoundary> + } +} diff --git a/src/components/checkbox_placeholder.rs b/src/components/checkbox_placeholder.rs new file mode 100644 index 0000000..a1aaa0c --- /dev/null +++ b/src/components/checkbox_placeholder.rs @@ -0,0 +1,54 @@ +use leptos::{ + IntoView, component, + html::Input, + prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute}, + view, +}; + +use crate::components::get_id; + +#[component] +pub fn CheckboxPlaceholder( + label: &'static str, + node_ref: NodeRef<Input>, +) -> impl IntoView { + let id = get_id(); + + view! { + <div class="relative h-14"> + <input + id=id.to_string() + type="checkbox" + autocomplete="off" + class="\ + absolute \ + bottom-0 \ + right-5 \ + bg-gray-200 \ + border-8 \ + border-b-2 \ + border-b-trasparent \ + border-gray-200 \ + focus:outline-none \ + h-10 \ + rounded-t-lg \ + text-gray-900 \ + " + node_ref=node_ref + /> + + <label + for=id.to_string() + class="\ + bottom-10 \ + absolute \ + left-0 \ + text-gray-700 \ + text-sm \ + " + > + {label} + </label> + </div> + } +} diff --git a/src/components/container.rs b/src/components/container.rs index d6d2f03..3b56713 100644 --- a/src/components/container.rs +++ b/src/components/container.rs @@ -1,46 +1,38 @@ use leptos::{ IntoView, component, - prelude::{Children, ClassAttribute, ElementChild, OnAttribute}, + prelude::{Children, ClassAttribute, ElementChild}, view, }; -use leptos_router::{NavigateOptions, hooks::use_navigate}; +use leptos_router::components::A; #[component] pub fn Container( - header: impl IntoView, - buttons: Vec<(impl IntoView, &'static str)>, + header: impl IntoView + 'static, + buttons: Vec<(impl IntoView + 'static, &'static str)>, children: Children, ) -> impl IntoView { assert!(!buttons.is_empty()); - let first_button_path = buttons.first().expect("Should have at least on button").1; + // TODO: Add the direct link to the first button back. <2026-02-15> + // let first_button_path = buttons.first().expect("Should have at least on button").1; view! { - <button - type="button" - on:click=|_| { - use_navigate()(first_button_path, NavigateOptions::default()); - } - > - <div class="p-4 mt-4 mr-4 ml-4 md-2 text-justify rounded-lg border-gray-600 border"> - <p class="text-lg text-bold">{header}</p> - {children()} + <div class="p-4 mt-4 mr-4 ml-4 md-2 text-justify rounded-lg border-gray-600 border"> + <h2 class="text-lg text-bold">{header}</h2> + {children()} - <ul class="flex flex-row gap-1 pt-2 overflow-x-auto"> - {buttons - .into_iter() - .map(|(name, path)| { - view! { - <li class="bg-green-400/40 p-2 text-nowrap first:rounded-l-full last:rounded-r-full"> - <button on:click=move |_| { - use_navigate()(path, NavigateOptions::default()); - }>{name}</button> - </li> - } - }) - .collect::<Vec<_>>()} - </ul> - </div> - </button> + <ul class="flex flex-row gap-1 pt-2 overflow-x-auto"> + {buttons + .into_iter() + .map(|(name, path)| { + view! { + <li class="bg-green-400/40 p-2 text-nowrap first:rounded-l-full last:rounded-r-full"> + <A href=move || path.to_owned()>{name}</A> + </li> + } + }) + .collect::<Vec<_>>()} + </ul> + </div> } } diff --git a/src/components/inventory.rs b/src/components/inventory.rs index 275dd0b..31b1c12 100644 --- a/src/components/inventory.rs +++ b/src/components/inventory.rs @@ -23,8 +23,8 @@ pub fn Inventory() -> impl IntoView { producer = |products| { let products_num = products.len(); let plural_s = if products_num == 1 { "" } else { "s" }; - let products_value = 2; - let products_currency = "EUR"; + let products_value = -1; + let products_currency = "TODO"; view! { <p> diff --git a/src/components/login_wall.rs b/src/components/login_wall.rs new file mode 100644 index 0000000..fd5c64f --- /dev/null +++ b/src/components/login_wall.rs @@ -0,0 +1,42 @@ +use leptos::{ + IntoView, component, + error::Error, + prelude::{Children, IntoAny}, + view, +}; +use leptos_router::{NavigateOptions, hooks::use_navigate}; + +use crate::{ + api::{can_be_provisioned_wrapped, is_logged_in_wrapped}, + components::async_fetch::{AsyncFetch, AsyncResource}, +}; + +#[component] +pub fn LoginWall( + back: impl Fn() -> String + Send + Sync + 'static, + children: Children, +) -> impl IntoView { + view! { + { + AsyncFetch! { + @map_error_in_producer + from_resource = AsyncResource!( + () -> Result<(bool, bool), Error> { + Ok((can_be_provisioned_wrapped().await?, is_logged_in_wrapped().await?)) + } + ), + producer = |(can_be_provisioned, is_logged_in)| { + if is_logged_in { + children() + } else if can_be_provisioned { + use_navigate()(format!("/provision/?back={}", back()).as_str(), NavigateOptions::default()); + ().into_any() + } else { + use_navigate()(format!("/login/?back={}", back()).as_str(), NavigateOptions::default()); + ().into_any() + } + } + } + } + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 2c3d79a..2a3a0b1 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,15 +3,22 @@ use std::sync::atomic::{AtomicU32, Ordering}; // Generic pub mod async_fetch; pub mod banner; +pub mod catch_errors; pub mod container; pub mod form; pub mod icon_p; +pub mod login_wall; + +// placeholders +pub mod checkbox_placeholder; pub mod input_placeholder; pub mod select_placeholder; +pub mod textarea_placeholder; // Specific pub mod inventory; pub mod product_overview; +pub mod product_parent_overview; pub mod recipies; pub mod site_header; pub mod unit_overview; diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs index bf81624..233b8a7 100644 --- a/src/components/product_overview.rs +++ b/src/components/product_overview.rs @@ -11,9 +11,20 @@ pub fn ProductOverview() -> impl IntoView { <Container header="Products" buttons=vec![ - (view! { <IconP icon=icondata_io::IoClipboard text="Show products" /> }, "products"), - (view! { <IconP icon=icondata_io::IoPricetags text="Create product" /> }, "create-product"), - (view! { <IconP icon=icondata_io::IoPricetags text="Associate barcode with product" /> }, "associate-barcode-product"), + (view! { <IconP icon=icondata_io::IoClipboard text="Show" /> }, "products"), + ( + view! { <IconP icon=icondata_io::IoPricetags text="Create product" /> }, + "create-product", + ), + ( + view! { + <IconP + icon=icondata_io::IoPricetags + text="Associate barcode with product" + /> + }, + "associate-barcode-product", + ), ] > { @@ -22,7 +33,7 @@ pub fn ProductOverview() -> impl IntoView { fetcher = products_registered_wrapped(), producer = |products| { view! { - <p>{format!("You have {} products", products.len())}</p> + <p>{format!("You have {} products.", products.len())}</p> } } } diff --git a/src/components/product_parent_overview.rs b/src/components/product_parent_overview.rs new file mode 100644 index 0000000..4aa2a0f --- /dev/null +++ b/src/components/product_parent_overview.rs @@ -0,0 +1,37 @@ +use leptos::{IntoView, component, view}; + +use crate::{ + api::product_parents_wrapped, + components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP}, +}; + +#[component] +pub fn ProductParentOverview() -> impl IntoView { + view! { + <Container + header="Products Parents" + buttons=vec![ + ( + view! { <IconP icon=icondata_io::IoClipboard text="Show products" /> }, + "products", + ), + ( + view! { <IconP icon=icondata_io::IoPricetags text="Create product parent" /> }, + "create-product-parent", + ), + ] + > + { + AsyncFetch! { + @map_error_in_producer + fetcher = product_parents_wrapped(), + producer = |product_parents| { + view! { + <p>{format!("You have {} product parents.", product_parents.len())}</p> + } + } + } + } + </Container> + } +} diff --git a/src/components/recipies.rs b/src/components/recipies.rs index f7903e4..755954e 100644 --- a/src/components/recipies.rs +++ b/src/components/recipies.rs @@ -1,6 +1,9 @@ -use leptos::{IntoView, component, prelude::ElementChild, view}; +use leptos::{IntoView, component, view}; -use crate::components::{container::Container, icon_p::IconP}; +use crate::{ + api::recipes_wrapped, + components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP}, +}; #[component] pub fn Recipies() -> impl IntoView { @@ -9,10 +12,21 @@ pub fn Recipies() -> impl IntoView { header="Recipies" buttons=vec![ (view! { <IconP icon=icondata_io::IoFastFood text="Recipies" /> }, "recipies"), + (view! { <IconP icon=icondata_io::IoPin text="Create recipe" /> }, "create-recipe"), (view! { <IconP icon=icondata_io::IoCalendarSharp text="Mealplan" /> }, "mealplan"), ] > - <p>"You have 0 recipies."</p> + { + AsyncFetch! { + @map_error_in_producer + fetcher = recipes_wrapped(), + producer = |recipes| { + view! { + <p>{format!("You have {} recipies.", recipes.len())}</p> + } + } + } + } </Container> } } diff --git a/src/components/textarea_placeholder.rs b/src/components/textarea_placeholder.rs new file mode 100644 index 0000000..a0bae6d --- /dev/null +++ b/src/components/textarea_placeholder.rs @@ -0,0 +1,60 @@ +use leptos::{ + IntoView, component, + html::Textarea, + prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute}, + view, +}; + +use crate::components::get_id; + +#[component] +pub fn TextareaPlaceholder( + label: &'static str, + node_ref: NodeRef<Textarea>, + #[prop(default = None)] initial_value: Option<String>, +) -> impl IntoView { + let id = get_id(); + + view! { + <div class="relative h-80"> + <textarea + id=id.to_string() + class="\ + absolute \ + bottom-0 \ + bg-gray-200 \ + border-2 \ + border-b-2 \ + border-b-trasparent \ + border-gray-200 \ + focus:border-indigo-600 \ + focus:border-b-transparent \ + focus:outline-none \ + h-[300px] \ + placeholder-transparent \ + rounded-t-lg \ + text-gray-900 \ + w-full \ + " + placeholder="sentinel value" + node_ref=node_ref + > + {initial_value} + </textarea> + + <label + for=id.to_string() + class="\ + absolute \ + transition-all \ + text-sm \ + text-gray-700 \ + top-0 \ + left-0 \ + " + > + {label} + </label> + </div> + } +} diff --git a/src/components/unit_overview.rs b/src/components/unit_overview.rs index 25e5675..0ea3825 100644 --- a/src/components/unit_overview.rs +++ b/src/components/unit_overview.rs @@ -1,6 +1,10 @@ use leptos::{IntoView, component, view}; +use rocie_client::models::{Unit, UnitProperty}; -use crate::components::{container::Container, icon_p::IconP}; +use crate::{ + api::{unit_properties_wrapped, units_wrapped}, + components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP}, +}; #[component] pub fn UnitOverview() -> impl IntoView { @@ -8,14 +12,37 @@ pub fn UnitOverview() -> impl IntoView { <Container header="Units" buttons=vec![ - (view! { <IconP icon=icondata_io::IoClipboard text="Show unit" /> }, "units"), - (view! { <IconP icon=icondata_io::IoClipboard text="Create unit" /> }, "create-unit"), - (view! { <IconP icon=icondata_io::IoPricetags text="Create unit property" /> }, "create-unit-property"), + (view! { <IconP icon=icondata_io::IoClipboard text="Show" /> }, "units"), + ( + view! { <IconP icon=icondata_io::IoClipboard text="Create unit" /> }, + "create-unit", + ), + ( + view! { <IconP icon=icondata_io::IoPricetags text="Create unit property" /> }, + "create-unit-property", + ), ] > { - "You have units" + AsyncFetch! { + @map_error_in_producer + fetcher = get_units_and_unit_properties(), + producer = |(units, unit_properties)| { + view! { + <p>{move || format!( + "You have {} units and {} unit properties.", + units.len(), + unit_properties.len() + )}</p> + } + }, + } } </Container> } } + +async fn get_units_and_unit_properties() +-> Result<(Vec<Unit>, Vec<UnitProperty>), leptos::error::Error> { + Ok((units_wrapped().await?, unit_properties_wrapped().await?)) +} |
