summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/mod.rs50
-rw-r--r--src/components/async_fetch.rs50
-rw-r--r--src/components/container.rs47
-rw-r--r--src/components/icon_p.rs18
-rw-r--r--src/components/inventory.rs55
-rw-r--r--src/components/mod.rs6
-rw-r--r--src/components/product_overview.rs71
-rw-r--r--src/components/recipies.rs12
-rw-r--r--src/components/side_header.rs22
-rw-r--r--src/components/site_header.rs29
-rw-r--r--src/lib.rs41
-rw-r--r--src/main.rs5
-rw-r--r--src/pages/home.rs24
13 files changed, 321 insertions, 109 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
new file mode 100644
index 0000000..8b9e77d
--- /dev/null
+++ b/src/api/mod.rs
@@ -0,0 +1,50 @@
+use leptos::{
+    error::Error,
+    prelude::{Read, expect_context},
+};
+use reactive_stores::Store;
+use rocie_client::{
+    apis::{
+        api_get_inventory_api::amount_by_id,
+        api_get_product_api::{product_by_id, products},
+        api_get_unit_api::unit_by_id,
+    },
+    models::{Product, ProductAmount, ProductId, Unit, UnitId},
+};
+
+use crate::{ConfigState, ConfigStateStoreFields};
+
+pub(crate) async fn get_amount_by_id(product_id: ProductId) -> Result<ProductAmount, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    amount_by_id(&config.config().read(), product_id)
+        .await
+        .map_err(Into::<Error>::into)
+}
+pub(crate) async fn get_product_by_id(product_id: ProductId) -> Result<Product, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    product_by_id(&config.config().read(), product_id)
+        .await
+        .map_err(Into::<Error>::into)
+}
+pub(crate) async fn get_unit_by_id(unit_id: UnitId) -> Result<Unit, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    unit_by_id(&config.config().read(), unit_id)
+        .await
+        .map_err(Into::<Error>::into)
+}
+pub(crate) async fn get_full_product_by_id(
+    id: ProductId,
+) -> Result<(Product, ProductAmount, Unit), Error> {
+    let amount = get_amount_by_id(id).await?;
+    let product = get_product_by_id(id).await?;
+    let unit = get_unit_by_id(amount.amount.unit).await?;
+
+    Ok::<_, Error>((product, amount, unit))
+}
+
+pub(crate) async fn get_products() -> Result<Vec<Product>, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    products(&config.config().read())
+        .await
+        .map_err(Into::<Error>::into)
+}
diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs
new file mode 100644
index 0000000..7105c6f
--- /dev/null
+++ b/src/components/async_fetch.rs
@@ -0,0 +1,50 @@
+macro_rules! AsyncFetch {
+    (fetcher = $fetcher:block producer = |$bound_variable:pat_param| $producer:block) => {{
+        use leptos::{
+            prelude::{ElementChild, LocalResource, Suspend, Transition},
+            view,
+        };
+
+        view! {
+            <Transition fallback=|| {
+                view! { <p>"Loading..."</p> }
+            }>
+                {move || Suspend::new(async move {
+                    let resource = { LocalResource::new(move || $fetcher) };
+                    resource
+                        .await
+                        .map(|$bound_variable| $producer)
+                })}
+            </Transition>
+        }
+    }};
+}
+pub(crate) use AsyncFetch;
+
+// #[component]
+// pub fn AsyncFetch<P, V, T, Fut>(
+//     fetcher: impl Fn() -> Fut + 'static + Send + Sync,
+//     producer: P,
+// ) -> impl IntoView
+// where
+//     V: IntoView + 'static,
+//     P: Fn(T) -> V + 'static + Send + Sync,
+//     Fut: Future<Output = Result<T, Error>> + 'static,
+//     T: 'static,
+//     LocalResource<Result<T, Error>>: IntoFuture<Output = Result<T, Error>> + Send,
+// {
+//     view! {
+//             <Transition fallback=|| {
+//                 view! { <p>"Loading..."</p> }
+//             }>
+//                 { || Suspend::new(async {
+//                     let value_resource = LocalResource::new( || fetcher());
+//                     value_resource
+//                         .await
+//                         .map(|value| {
+//                             producer(value)
+//                         })
+//                 })}
+//             </Transition>
+//     }
+// }
diff --git a/src/components/container.rs b/src/components/container.rs
index cf7aa5a..7a4a64f 100644
--- a/src/components/container.rs
+++ b/src/components/container.rs
@@ -1,34 +1,35 @@
 use leptos::{
     IntoView, component,
-    prelude::{Children, ClassAttribute, ElementChild},
+    prelude::{Children, ClassAttribute, ElementChild, OnAttribute},
     view,
 };
-use leptos_meta::Style;
+use leptos_router::{NavigateOptions, hooks::use_navigate};
 
 #[component]
-pub fn Container(header: impl IntoView, children: Children) -> impl IntoView {
+pub fn Container(
+    header: impl IntoView,
+    buttons: Vec<(impl IntoView, &'static str)>,
+    children: Children,
+) -> impl IntoView {
     view! {
-        <Style>
-            "
-            .rocie-container {
-                border-width: 0.1rem;
-                border-style: solid;
-                border-color: gray;
-                border-radius: 15%;
-
-                padding: 0.2rem;
-
-                text-align: left;
-            }
-            .rocie-container-header {
-                font-size: 1.5rem;
-            }
-            "
-        </Style>
-
-        <div class="rocie-container">
-            <p class="rocie-container-header">{header}</p>
+        <div class="p-4 mt-4 mr-4 ml-4 md-2 rounded-lg border-gray-600 border">
+            <p class="text-lg text-bold">{header}</p>
             {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 first:rounded-l-full last:rounded-r-full">
+                                <button on:click=move |_| {
+                                    use_navigate()(path, NavigateOptions::default());
+                                }>{name}</button>
+                            </li>
+                        }
+                    })
+                    .collect::<Vec<_>>()}
+            </ul>
         </div>
     }
 }
diff --git a/src/components/icon_p.rs b/src/components/icon_p.rs
new file mode 100644
index 0000000..372e280
--- /dev/null
+++ b/src/components/icon_p.rs
@@ -0,0 +1,18 @@
+use leptos::{
+    IntoView, component,
+    prelude::{ClassAttribute, ElementChild, Signal},
+    view,
+};
+use leptos_icons::Icon;
+
+#[component]
+pub fn IconP(#[prop(into)] icon: Signal<icondata_core::Icon>, text: &'static str) -> impl IntoView {
+    view! {
+        <div class="flex justify-evenly items-center">
+            <div class="mr-1">
+                <Icon icon=icon />
+            </div>
+            <p>{text}</p>
+        </div>
+    }
+}
diff --git a/src/components/inventory.rs b/src/components/inventory.rs
new file mode 100644
index 0000000..5855b33
--- /dev/null
+++ b/src/components/inventory.rs
@@ -0,0 +1,55 @@
+use leptos::{
+    IntoView, component,
+    prelude::{ClassAttribute, ElementChild},
+    view,
+};
+
+use crate::{
+    api::{get_full_product_by_id, get_products},
+    components::{async_fetch::AsyncFetch, site_header::SiteHeader},
+};
+
+#[component]
+pub fn Inventory() -> impl IntoView {
+    view! {
+        <SiteHeader logo=icondata_io::IoArrowBack back_location="/" name="Inventory" />
+
+        <ul class="flex flex-col p-2 m-2">
+            {
+                AsyncFetch! {
+                    fetcher = {get_products()}
+                    producer = |products| {
+                        products
+                         .into_iter()
+                         .map(|product| {
+                            view! {
+                                {AsyncFetch! {
+                                    fetcher = {get_full_product_by_id(product.id)}
+                                    producer = |(product, amount, unit)| {
+                                      view! {
+                                          <ul class="my-3">
+                                              <li class="m-2">{product.name}</li>
+                                              <li class="m-2">
+                                                  <span class="bg-gray-200 p-1 px-2 rounded-lg">
+                                                      {
+                                                        format!(
+                                                            "{} {}",
+                                                            amount.amount.value,
+                                                            unit.short_name
+                                                            )
+                                                        }
+                                                  </span>
+                                              </li>
+                                          </ul>
+                                      }
+                                    }
+                                }}
+                            }
+                        })
+                        .collect::<Vec<_>>()
+                    }
+                }
+            }
+        </ul>
+    }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 85f9671..7174ad8 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -1,6 +1,10 @@
 // Generic
+pub mod async_fetch;
 pub mod container;
+pub mod icon_p;
 
 // Specific
+pub mod inventory;
 pub mod product_overview;
-pub mod side_header;
+pub mod recipies;
+pub mod site_header;
diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs
index 4e95335..ae2eaf2 100644
--- a/src/components/product_overview.rs
+++ b/src/components/product_overview.rs
@@ -1,47 +1,40 @@
-use std::sync::Arc;
+use leptos::{IntoView, component, view};
 
-use leptos::{
-    IntoView, component,
-    prelude::{ElementChild, Set, signal},
-    task::spawn_local,
-    view,
+use crate::{
+    api::get_products,
+    components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP},
 };
-use rocie_client::apis::{api_get_product_api::products, configuration::Configuration};
-
-use crate::components::container::Container;
 
 #[component]
-pub fn ProductOverview(config: Arc<Configuration>) -> impl IntoView {
-    let (read_status, write_status) = signal("Loading..".to_owned());
-
-    {
-        let local_config = Arc::clone(&config);
-
-        spawn_local(async move {
-            let products = products(&local_config).await;
-
-            write_status.set(
-                products
-                    .as_ref()
-                    .map(move |products| {
-                        let products_num = products.len();
-                        let plural_s = if products_num == 1 { "" } else { "s" };
-                        let products_value = 2;
-                        let products_currency = "EUR";
-                        format!(
-                            "You have {products_num} product{plural_s} \
-                         in stock with a value \
-                         of {products_value} {products_currency}.",
-                        )
-                    })
-                    .unwrap(),
-            );
-        });
-    }
-
+pub fn ProductOverview() -> impl IntoView {
     view! {
-        <Container header="Inventory">
-            <p>{read_status}</p>
+        <Container
+            header="Inventory"
+            buttons=vec![
+                (view! { <IconP icon=icondata_io::IoClipboardOutline text="Inventory" /> }, "inventory"),
+                (view! { <IconP icon=icondata_io::IoPricetags text="Consume" /> }, "consume"),
+                (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"),
+            ]
+        >
+            {AsyncFetch!(
+                fetcher = {get_products()}
+                producer = |products| {
+                  let products_num = products.len();
+                  let plural_s = if products_num == 1 { "" } else { "s" };
+                  let products_value = 2;
+                  let products_currency = "EUR";
+
+                  view! {
+                      <p>
+                          {format!(
+                              "You have {products_num} product{plural_s} \
+                                   in stock with a value \
+                                   of {products_value} {products_currency}.",
+                          )}
+                      </p>
+                  }
+            }
+            )}
         </Container>
     }
 }
diff --git a/src/components/recipies.rs b/src/components/recipies.rs
new file mode 100644
index 0000000..1bd3a0d
--- /dev/null
+++ b/src/components/recipies.rs
@@ -0,0 +1,12 @@
+use leptos::{IntoView, component, prelude::ElementChild, view};
+
+use crate::components::container::Container;
+
+#[component]
+pub fn Recipies() -> impl IntoView {
+    view! {
+        <Container header="Recipies" buttons=vec![("Mealplan", "mealplan")]>
+            <p>"You have 0 recipies."</p>
+        </Container>
+    }
+}
diff --git a/src/components/side_header.rs b/src/components/side_header.rs
deleted file mode 100644
index 9cd6777..0000000
--- a/src/components/side_header.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use leptos::prelude::{AddAnyAttr, ElementChild, IntoView, StyleAttribute, component, view};
-use leptos_router::{NavigateOptions, hooks::use_navigate};
-use thaw::{Flex, FlexJustify, LayoutHeader};
-
-#[component]
-pub fn SiteHeader() -> impl IntoView {
-    let navigate = use_navigate();
-
-    view! {
-        <LayoutHeader
-            attr:style="padding: 20px;"
-            on:click=move |_| {
-                navigate("/", NavigateOptions::default());
-            }
-        >
-            <Flex justify=FlexJustify::SpaceAround>
-                <img src="/logo.svg" style="width: 36px" />
-                <h3>"Rocie"</h3>
-            </Flex>
-        </LayoutHeader>
-    }
-}
diff --git a/src/components/site_header.rs b/src/components/site_header.rs
new file mode 100644
index 0000000..65f7137
--- /dev/null
+++ b/src/components/site_header.rs
@@ -0,0 +1,29 @@
+use icondata_core::Icon as DataIcon;
+use leptos::prelude::{ClassAttribute, ElementChild, IntoView, OnAttribute, component, view};
+use leptos_icons::Icon;
+use leptos_router::{NavigateOptions, hooks::use_navigate};
+
+#[component]
+pub fn SiteHeader(
+    logo: DataIcon,
+    back_location: &'static str,
+    name: &'static str,
+    #[prop(default = None)] menu: Option<String>,
+) -> impl IntoView {
+    let navigate = use_navigate();
+
+    view! {
+        <div
+            class="flex flex-row justify-between items-center p-2 px-4 w-full h-1/12 bg-gray-100"
+            on:click=move |_| {
+                navigate(back_location, NavigateOptions::default());
+            }
+        >
+            <div class="max-w-14">
+                <Icon icon=logo />
+            </div>
+            <p class="text-xl">{name}</p>
+            <div>{menu}</div>
+        </div>
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 357ce93..a488d95 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,24 +7,38 @@
     reason = "Can't add it to leptos' components"
 )]
 #![expect(
-    clippy::needless_pass_by_value,
-    reason = "Can't add it to leptos' components"
+    unused_extern_crates,
+    reason = "Deependency needed to inject the `js` feature into uuid"
 )]
+extern crate uuid;
+
+// All of them are only used in the `main.rs` and not in the `lib.rs` part of this crate.
+extern crate console_error_panic_hook;
+extern crate console_log;
+extern crate log;
 
+mod api;
 mod components;
 mod pages;
 
-use std::sync::Arc;
-
-use leptos::prelude::{AddAnyAttr, IntoView, component, view};
+use leptos::prelude::{AddAnyAttr, IntoView, component, provide_context, view};
 use leptos_meta::{Html, Meta, Title, provide_meta_context};
 use leptos_router::{
     components::{Route, Router, Routes},
     path,
 };
+use reactive_stores::Store;
 use rocie_client::apis::configuration::Configuration;
 
-use crate::pages::{home::Home, not_found::NotFound};
+use crate::{
+    components::inventory::Inventory,
+    pages::{home::Home, not_found::NotFound},
+};
+
+#[derive(Debug, Clone, Store)]
+pub struct ConfigState {
+    config: Configuration,
+}
 
 #[component]
 pub fn App() -> impl IntoView {
@@ -37,13 +51,15 @@ pub fn App() -> impl IntoView {
         config.user_agent = Some("rocie-mobile".to_owned());
         "http://127.0.0.1:8080".clone_into(&mut config.base_path);
 
-        Arc::new(config)
+        config
     };
 
+    provide_context(Store::new(ConfigState { config }));
+
     view! {
         <Html attr:lang="en" attr:dir="ltr" attr:data-theme="light" />
 
-        <Title text="Welcome to Leptos CSR" />
+        <Title text="Rocie-mobile" />
 
         <Meta charset="UTF-8" />
         <Meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -53,8 +69,13 @@ pub fn App() -> impl IntoView {
                 <Route
                     path=path!("/")
                     view=move || {
-                        let local_config = Arc::clone(&config);
-                        view! { <Home config=local_config /> }
+                        view! { <Home /> }
+                    }
+                />
+                <Route
+                    path=path!("/inventory")
+                    view=move || {
+                        view! { <Inventory /> }
                     }
                 />
             </Routes>
diff --git a/src/main.rs b/src/main.rs
index c377484..c3eaadf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,8 @@
+#![expect(
+    unused_crate_dependencies,
+    reason = "We use them in the lib version of this crate"
+)]
+
 use leptos::prelude::{mount_to_body, view};
 
 use rocie_mobile::App;
diff --git a/src/pages/home.rs b/src/pages/home.rs
index 8749860..387562e 100644
--- a/src/pages/home.rs
+++ b/src/pages/home.rs
@@ -1,26 +1,23 @@
-use std::sync::Arc;
-
 use leptos::{
     IntoView, component,
     error::ErrorBoundary,
-    prelude::{CollectView, ElementChild, Get, GetUntracked},
+    prelude::{ClassAttribute, CollectView, ElementChild, Get, GetUntracked},
     view,
 };
 use leptos_router::{
     NavigateOptions,
     hooks::{use_navigate, use_query_map},
 };
-use rocie_client::apis::configuration::Configuration;
-use thaw::{Layout, LayoutPosition};
 
-use crate::components::{product_overview::ProductOverview, side_header::SiteHeader};
+use crate::components::{
+    product_overview::ProductOverview, recipies::Recipies, site_header::SiteHeader,
+};
 
 #[component]
-pub fn Home(config: Arc<Configuration>) -> impl IntoView {
+pub fn Home() -> impl IntoView {
     let query_map = use_query_map().get_untracked();
     let navigate = use_navigate();
 
-    // mobile page
     if let Some(path) = query_map.get("path") {
         navigate(&path, NavigateOptions::default());
     }
@@ -44,12 +41,11 @@ pub fn Home(config: Arc<Configuration>) -> impl IntoView {
             }
         }>
 
-            <Layout position=LayoutPosition::Absolute>
-                <SiteHeader />
-                <Layout>
-                    <ProductOverview config />
-                </Layout>
-            </Layout>
+            <div class="flex flex-col content-start">
+                <SiteHeader logo=icondata_io::IoRoseSharp back_location="/" name="Rocie" />
+                <ProductOverview />
+                <Recipies />
+            </div>
         </ErrorBoundary>
     }
 }