From 7bff22756beec82b4a1470e2d325b706dc56e5f2 Mon Sep 17 00:00:00 2001
From: Benedikt Peetz
Date: Thu, 23 Oct 2025 01:36:39 +0200
Subject: feat(buy): Provide basic buy interface
---
src/components/async_fetch.rs | 67 ++++++-----
src/components/banner.rs | 17 +++
src/components/buy.rs | 212 ++++++++++++++++++++++++++++++-----
src/components/input_placeholder.rs | 154 ++++++++++++++++++++++---
src/components/mod.rs | 1 +
src/components/product_overview.rs | 8 +-
src/components/select_placeholder.rs | 40 ++++---
7 files changed, 403 insertions(+), 96 deletions(-)
create mode 100644 src/components/banner.rs
(limited to 'src/components')
diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs
index 7105c6f..f24e3a5 100644
--- a/src/components/async_fetch.rs
+++ b/src/components/async_fetch.rs
@@ -1,5 +1,23 @@
+macro_rules! AsyncResource {
+ (
+ (
+ $(
+ $input_name:ident : $input_type:ty = $input:expr
+ ),*
+ ) -> $output:ty $fetcher:block
+ ) => {{
+ async fn fetcher($($input_name : $input_type),*) -> $output $fetcher
+
+ leptos::prelude::LocalResource::new(move || fetcher($($input),*))
+ }}
+}
+pub(crate) use AsyncResource;
+
macro_rules! AsyncFetch {
- (fetcher = $fetcher:block producer = |$bound_variable:pat_param| $producer:block) => {{
+ (
+ fetcher = $fetcher:block
+ producer = |$bound_variable:pat_param| $producer:block
+ ) => {{
use leptos::{
prelude::{ElementChild, LocalResource, Suspend, Transition},
view,
@@ -18,33 +36,24 @@ macro_rules! AsyncFetch {
}
}};
+ (
+ @map_error_in_producer
+ from_resource = $resource:ident
+ producer = |$bound_variable:pat_param| $producer:block
+ ) => {{
+ use leptos::prelude::{ElementChild, Suspend, Transition};
+
+ leptos::view! {
+ "Loading..."
}
+ }>
+ {move || Suspend::new(async move {
+ $resource
+ .await
+ .map(|$bound_variable| $producer)
+ })}
+
+ }
+ }};
}
pub(crate) use AsyncFetch;
-
-// #[component]
-// pub fn AsyncFetch(
-// fetcher: impl Fn() -> Fut + 'static + Send + Sync,
-// producer: P,
-// ) -> impl IntoView
-// where
-// V: IntoView + 'static,
-// P: Fn(T) -> V + 'static + Send + Sync,
-// Fut: Future
}
-// }>
-// { || Suspend::new(async {
-// let value_resource = LocalResource::new( || fetcher());
-// value_resource
-// .await
-// .map(|value| {
-// producer(value)
-// })
-// })}
-//
-// }
-// }
diff --git a/src/components/banner.rs b/src/components/banner.rs
new file mode 100644
index 0000000..acaaf62
--- /dev/null
+++ b/src/components/banner.rs
@@ -0,0 +1,17 @@
+use leptos::{
+ IntoView, component,
+ prelude::{ClassAttribute, ElementChild},
+ view,
+};
+
+#[component]
+pub fn Banner(mut text: T) -> impl IntoView
+where
+ T: FnMut() -> String + Send + 'static,
+{
+ view! {
+
+ {move || text()}
+
+ }
+}
diff --git a/src/components/buy.rs b/src/components/buy.rs
index 6d9402e..cb4cff4 100644
--- a/src/components/buy.rs
+++ b/src/components/buy.rs
@@ -1,41 +1,193 @@
-use leptos::{IntoView, component, view};
+use leptos::{
+ IntoView, component,
+ prelude::{Get, Read, Show, WriteSignal, expect_context, signal},
+ task::spawn_local,
+ view,
+};
+use leptos_router::{NavigateOptions, hooks::use_navigate};
use log::info;
-use rocie_client::models::UnitId;
+use reactive_stores::Store;
+use rocie_client::{
+ apis::Error,
+ models::{Product, Unit},
+};
use uuid::Uuid;
-use crate::components::{form::Form, site_header::SiteHeader};
+use crate::{
+ ConfigState, ConfigStateStoreFields,
+ api::{
+ buy_barcode_wrapper, get_product_by_name, get_product_unit_by_id,
+ get_products_by_part_name, get_unit_by_id,
+ },
+ components::{async_fetch::AsyncResource, banner::Banner, form::Form, site_header::SiteHeader},
+};
#[component]
pub fn Buy() -> impl IntoView {
+ let (on_submit_errored, on_submit_errored_set) = signal(None);
+
+ view! {
+
+
+
+
+
+
+ {
+ Form! {
+ on_submit = |barcode_number, amount| {
+ let config = expect_context::>();
+ let config = config.config().read();
+
+ spawn_local(async move {
+ if let Err(err) = buy_barcode_wrapper(&config, barcode_number).await {
+ let error = format!("Error in form on-submit for barcode `{barcode_number}`: {err}");
+ on_submit_errored_set.set(Some(error));
+ } else {
+ on_submit_errored_set.set(None);
+
+ info!("Bought barcode {barcode_number} {amount} times");
+ }
+
+ });
+ };
+
+
+
+
+ }
+ }
+ }
+}
+
+#[component]
+pub fn AssociateBarcode() -> impl IntoView {
+ let product_name_signal;
+
+ let (show_units, show_units_set) = signal(false);
+
view! {
- {Form! {
- on_submit = |product_barcode, amount, unit_id| {
- info!("Got product barcode: {product_barcode} with amount: {amount}, {unit_id}");
- };
-
-
-
-
- }}
+ {
+ Form! {
+ on_submit = |product_name, amount, unit_id| {
+ spawn_local(async move {
+ let navigate = use_navigate();
+
+ info!("Got product barcode: {product_name} with amount: {amount}, and {unit_id}");
+
+ navigate("/", NavigateOptions::default());
+ });
+ };
+
+
+
+
+
+
+
+ }
+ }
+ }
+}
+
+async fn generate_suggest_products(
+ optional_product_name: Option,
+) -> Result