bazzar/crates/web/src/shopping_cart.rs

191 lines
5.8 KiB
Rust

use model::{PaymentMethod, 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<model::api::UpdateCartOutput>),
NotesChanged(String),
PaymentChanged(model::PaymentMethod),
}
impl From<CartMsg> for Msg {
fn from(msg: CartMsg) -> Self {
Msg::Cart(msg)
}
}
pub type Items = indexmap::IndexMap<ProductId, Item>;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Item {
#[serde(rename = "i")]
pub product_id: ProductId,
#[serde(rename = "q")]
pub quantity: Quantity,
#[serde(rename = "u")]
pub quantity_unit: QuantityUnit,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ShoppingCart {
#[serde(rename = "i")]
pub cart_id: Option<model::ShoppingCartId>,
#[serde(rename = "is")]
pub items: Items,
#[serde(default, rename = "pm")]
pub payment_method: Option<PaymentMethod>,
#[serde(default, rename = "cn")]
pub checkout_notes: String,
#[serde(skip)]
pub hover: bool,
}
pub fn init(model: &mut Model, _orders: &mut impl Orders<Msg>) {
model.cart = load_local();
}
pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
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.cart_id = Some(cart.cart_id);
model.cart.checkout_notes = cart.checkout_notes;
model.cart.payment_method = Some(cart.payment_method);
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::NotesChanged(notes) => {
model.cart.checkout_notes = notes;
store_local(&model.cart);
sync_cart(model, orders);
}
CartMsg::PaymentChanged(method) => {
model.cart.payment_method = Some(method);
store_local(&model.cart);
sync_cart(model, orders);
}
}
}
fn sync_cart(model: &mut Model, orders: &mut impl Orders<Msg>) {
if let Some(access_token) = model.shared.access_token.as_ref().cloned() {
let items: Vec<Item> = model.cart.items.values().map(Clone::clone).collect();
let notes = model.cart.checkout_notes.clone();
let payment_method = model.cart.payment_method;
orders.perform_cmd(async move {
crate::Msg::from(CartMsg::SyncResult(
crate::api::public::update_cart(access_token, items, notes, payment_method).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();
}