summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/components/async_fetch.rs12
-rw-r--r--src/components/buy.rs1
-rw-r--r--src/components/catch_errors.rs40
-rw-r--r--src/components/checkbox_placeholder.rs54
-rw-r--r--src/components/container.rs52
-rw-r--r--src/components/inventory.rs4
-rw-r--r--src/components/login_wall.rs42
-rw-r--r--src/components/mod.rs7
-rw-r--r--src/components/product_overview.rs19
-rw-r--r--src/components/product_parent_overview.rs37
-rw-r--r--src/components/recipies.rs20
-rw-r--r--src/components/textarea_placeholder.rs60
-rw-r--r--src/components/unit_overview.rs37
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?))
+}