summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-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>
}
}