use model::{ProductId, Quantity, QuantityUnit}; use seed::prelude::*; use serde::{Deserialize, Serialize}; use crate::shared::notification::NotificationMsg; use crate::{Model, Msg, NetRes}; #[derive(Debug)] pub enum CartMsg { AddItem { quantity: Quantity, quantity_unit: QuantityUnit, product_id: ProductId, }, ModifyItem { quantity: Quantity, quantity_unit: QuantityUnit, product_id: ProductId, }, Remove(ProductId), Hover, Leave, /// Send current non-empty cart to server Sync, SyncResult(NetRes), ChangeNotes(String), } impl From for Msg { fn from(msg: CartMsg) -> Self { Msg::Cart(msg) } } pub type Items = indexmap::IndexMap; #[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct Item { pub product_id: ProductId, pub quantity: Quantity, pub quantity_unit: QuantityUnit, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct ShoppingCart { pub cart_id: Option, pub items: Items, #[serde(default)] pub checkout_notes: String, #[serde(skip)] pub hover: bool, } pub fn init(model: &mut Model, _orders: &mut impl Orders) { model.cart = load_local(); } pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders) { match msg { CartMsg::AddItem { quantity, quantity_unit, product_id, } => { { let items: &mut Items = &mut model.cart.items; let entry: &mut Item = items.entry(product_id).or_insert_with(|| Item { quantity: Quantity::from_u32(0), quantity_unit, product_id, }); entry.quantity = entry.quantity + quantity; entry.quantity_unit = quantity_unit; } store_local(&model.cart); sync_cart(model, orders); } CartMsg::ModifyItem { product_id, quantity_unit, quantity, } => { if **quantity == 0 { model.cart.items.remove(&product_id); } else { let items: &mut Items = &mut model.cart.items; let entry: &mut Item = items.entry(product_id).or_insert_with(|| Item { quantity, quantity_unit, product_id, }); entry.quantity = quantity; entry.quantity_unit = quantity_unit; } store_local(&model.cart); sync_cart(model, orders); } CartMsg::Remove(product_id) => { model.cart.items.remove(&product_id); store_local(&model.cart); sync_cart(model, orders); } CartMsg::Hover => { model.cart.hover = true; } CartMsg::Leave => { model.cart.hover = false; } CartMsg::Sync => sync_cart(model, orders), CartMsg::SyncResult(NetRes::Success(cart)) => { let len = cart.items.len(); model.cart.items = cart.items.into_iter().fold( IndexMap::with_capacity(len), |mut set, model::api::ShoppingCartItem { id: _, product_id, shopping_cart_id: _, quantity, quantity_unit, }| { set.insert( product_id, Item { product_id, quantity, quantity_unit, }, ); set }, ); } CartMsg::SyncResult(NetRes::Error(failure)) => { for msg in failure.errors { orders.send_msg(NotificationMsg::Error(msg).into()); } } CartMsg::SyncResult(NetRes::Http(_cart)) => { orders.send_msg(NotificationMsg::Error("Unable to sync cart".into()).into()); } CartMsg::ChangeNotes(notes) => { model.cart.checkout_notes = notes; store_local(&model.cart); } } } fn sync_cart(model: &mut Model, orders: &mut impl Orders) { if let Some(access_token) = model.shared.access_token.as_ref().cloned() { let items: Vec = model.cart.items.values().map(Clone::clone).collect(); orders.perform_cmd(async { crate::Msg::from(CartMsg::SyncResult( crate::api::public::update_cart(access_token, items).await, )) }); } } fn load_local() -> ShoppingCart { match LocalStorage::get("ct") { Ok(cart) => cart, Err(e) => { seed::error!("Storage error", e); ShoppingCart::default() } } } fn store_local(cart: &ShoppingCart) { LocalStorage::insert("ct", cart).ok(); }