Add checkout notes

This commit is contained in:
Adrian Woźniak 2022-05-19 14:03:18 +02:00
parent b1b4d083b7
commit cafafb0f24
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
18 changed files with 131 additions and 72 deletions

View File

@ -1,3 +1,11 @@
#[package]
#name = "bazzar"
#version = "0.1.0"
#edition = "2021"
#
#[[bin]]
#name = "api"
#path = "./api/src/main.rs"
[workspace] [workspace]
members = [ members = [
"api", "api",

View File

@ -94,7 +94,7 @@ impl CartManager {
} }
} }
#[derive(actix::Message)] #[derive(actix::Message, Debug)]
#[rtype(result = "Result<Option<ShoppingCartItem>>")] #[rtype(result = "Result<Option<ShoppingCartItem>>")]
pub struct ModifyItem { pub struct ModifyItem {
pub buyer_id: AccountId, pub buyer_id: AccountId,
@ -196,7 +196,7 @@ pub(crate) async fn remove_product(
)) ))
} }
#[derive(actix::Message)] #[derive(actix::Message, Debug)]
#[rtype(result = "Result<Vec<ShoppingCartItem>>")] #[rtype(result = "Result<Vec<ShoppingCartItem>>")]
pub struct ModifyCart { pub struct ModifyCart {
pub buyer_id: AccountId, pub buyer_id: AccountId,
@ -206,6 +206,7 @@ pub struct ModifyCart {
cart_async_handler!(ModifyCart, modify_cart, Vec<ShoppingCartItem>); cart_async_handler!(ModifyCart, modify_cart, Vec<ShoppingCartItem>);
async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<ShoppingCartItem>> { async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<ShoppingCartItem>> {
log::debug!("{:?}", msg);
let _cart = query_db!( let _cart = query_db!(
db, db,
database_manager::EnsureActiveShoppingCart { database_manager::EnsureActiveShoppingCart {
@ -222,6 +223,7 @@ async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<S
passthrough Error::Db, passthrough Error::Db,
Error::CartNotAvailable Error::CartNotAvailable
); );
log::debug!("carts {:?}", carts);
let cart = if carts.is_empty() { let cart = if carts.is_empty() {
return Err(Error::CartNotAvailable); return Err(Error::CartNotAvailable);
} else { } else {
@ -236,7 +238,7 @@ async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<S
agg agg
}); });
let mut items: Vec<model::ShoppingCartItem> = query_db!( let items: Vec<model::ShoppingCartItem> = query_db!(
db, db,
database_manager::CartItems { database_manager::CartItems {
shopping_cart_id: cart.id shopping_cart_id: cart.id
@ -244,7 +246,10 @@ async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<S
Error::CantModifyCart Error::CantModifyCart
); );
for item in items.drain_filter(|item| !existing.contains(&item.product_id)) { for item in items
.into_iter()
.filter(|item| !existing.contains(&item.product_id))
{
query_db!( query_db!(
db, db,
database_manager::RemoveCartItem { database_manager::RemoveCartItem {
@ -256,27 +261,10 @@ async fn modify_cart(msg: ModifyCart, db: actix::Addr<Database>) -> Result<Vec<S
); );
} }
let mut out = Vec::with_capacity(items.len()); let mut out = Vec::with_capacity(msg.items.len());
for ShoppingCartItem { for item in msg.items {
id: _, if let Some(item) = modify_item(item, db.clone()).await? {
product_id,
shopping_cart_id: _,
quantity,
quantity_unit,
} in items
{
if let Some(item) = modify_item(
ModifyItem {
buyer_id: msg.buyer_id,
product_id,
quantity,
quantity_unit,
},
db.clone(),
)
.await?
{
out.push(item); out.push(item);
} }
} }

View File

@ -31,7 +31,7 @@ pub(crate) async fn all_account_orders(
) -> Result<Vec<AccountOrder>> { ) -> Result<Vec<AccountOrder>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
FROM account_orders FROM account_orders
ORDER BY id DESC ORDER BY id DESC
"#, "#,
@ -60,6 +60,7 @@ pub struct CreateAccountOrder {
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub items: Vec<create_order::OrderItem>, pub items: Vec<create_order::OrderItem>,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub checkout_notes: Option<String>,
} }
db_async_handler!( db_async_handler!(
@ -77,7 +78,7 @@ pub(crate) async fn create_account_order(
r#" r#"
INSERT INTO account_orders (buyer_id, status) INSERT INTO account_orders (buyer_id, status)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
RETURNING id, buyer_id, status, order_ext_id, service_order_id RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
"#, "#,
) )
.bind(msg.buyer_id) .bind(msg.buyer_id)
@ -113,6 +114,7 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id
ShoppingCartSetState { ShoppingCartSetState {
id: msg.shopping_cart_id, id: msg.shopping_cart_id,
state: ShoppingCartState::Closed, state: ShoppingCartState::Closed,
checkout_notes: msg.checkout_notes,
}, },
t, t,
) )
@ -146,7 +148,7 @@ pub(crate) async fn update_account_order(
UPDATE account_orders UPDATE account_orders
SET buyer_id = $2 AND status = $3 AND order_id = $4 SET buyer_id = $2 AND status = $3 AND order_id = $4
WHERE id = $1 WHERE id = $1
RETURNING id, buyer_id, status, order_ext_id, service_order_id RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
"#, "#,
) )
.bind(msg.id) .bind(msg.id)
@ -183,7 +185,7 @@ pub(crate) async fn update_account_order_by_ext(
UPDATE account_orders UPDATE account_orders
SET status = $2 SET status = $2
WHERE order_ext_id = $1 WHERE order_ext_id = $1
RETURNING id, buyer_id, status, order_ext_id, service_order_id RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
"#, "#,
) )
.bind(msg.order_ext_id) .bind(msg.order_ext_id)
@ -207,7 +209,7 @@ db_async_handler!(FindAccountOrder, find_account_order, AccountOrder);
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> { pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
FROM account_orders FROM account_orders
WHERE id = $1 WHERE id = $1
"#, "#,
@ -239,7 +241,7 @@ pub(crate) async fn set_order_service_id(
UPDATE account_orders UPDATE account_orders
SET service_order_id = $2 SET service_order_id = $2
WHERE id = $1 WHERE id = $1
RETURNING id, buyer_id, status, order_ext_id, service_order_id RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
"#, "#,
) )
.bind(msg.id) .bind(msg.id)

View File

@ -172,7 +172,7 @@ pub(crate) async fn update_shopping_cart_item(
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE shopping_cart_items UPDATE shopping_cart_items
SET product_id = $2 AND shopping_cart_id = $3 AND quantity = $4 AND quantity_unit = $5 SET product_id = $2, shopping_cart_id = $3, quantity = $4, quantity_unit = $5
WHERE id = $1 WHERE id = $1
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
"#, "#,

View File

@ -30,7 +30,7 @@ pub(crate) async fn all_shopping_carts(
) -> Result<Vec<ShoppingCart>> { ) -> Result<Vec<ShoppingCart>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, payment_method, state SELECT id, buyer_id, payment_method, state, checkout_notes
FROM shopping_carts FROM shopping_carts
"#, "#,
) )
@ -62,9 +62,9 @@ pub(crate) async fn account_shopping_carts(
if let Some(state) = msg.state { if let Some(state) = msg.state {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, payment_method, state SELECT id, buyer_id, payment_method, state, checkout_notes
FROM shopping_carts FROM shopping_carts
WHERE buyer_id = $1 AND state = $2 WHERE buyer_id = $1 AND state = $2
"#, "#,
) )
.bind(msg.account_id) .bind(msg.account_id)
@ -72,7 +72,7 @@ SELECT id, buyer_id, payment_method, state
} else { } else {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, payment_method, state SELECT id, buyer_id, payment_method, state, checkout_notes
FROM shopping_carts FROM shopping_carts
WHERE buyer_id = $1 WHERE buyer_id = $1
"#, "#,
@ -104,7 +104,7 @@ pub(crate) async fn create_shopping_cart(
r#" r#"
INSERT INTO shopping_carts (buyer_id, payment_method) INSERT INTO shopping_carts (buyer_id, payment_method)
VALUES ($1, $2) VALUES ($1, $2)
RETURNING id, buyer_id, payment_method, state RETURNING id, buyer_id, payment_method, state, checkout_notes
"#, "#,
) )
.bind(msg.buyer_id) .bind(msg.buyer_id)
@ -137,7 +137,7 @@ pub(crate) async fn update_shopping_cart(
UPDATE shopping_carts UPDATE shopping_carts
SET buyer_id = $2 AND payment_method = $2 AND state = $4 SET buyer_id = $2 AND payment_method = $2 AND state = $4
WHERE id = $1 WHERE id = $1
RETURNING id, buyer_id, payment_method, state RETURNING id, buyer_id, payment_method, state, checkout_notes
"#, "#,
) )
.bind(msg.id) .bind(msg.id)
@ -157,38 +157,31 @@ RETURNING id, buyer_id, payment_method, state
pub struct ShoppingCartSetState { pub struct ShoppingCartSetState {
pub id: ShoppingCartId, pub id: ShoppingCartId,
pub state: ShoppingCartState, pub state: ShoppingCartState,
pub checkout_notes: Option<String>,
} }
db_async_handler!( db_async_handler!(
ShoppingCartSetState, ShoppingCartSetState,
inner_shopping_cart_set_state, shopping_cart_set_state,
ShoppingCart ShoppingCart,
inner_shopping_cart_set_state
); );
async fn inner_shopping_cart_set_state( pub(crate) async fn shopping_cart_set_state(
msg: ShoppingCartSetState, msg: ShoppingCartSetState,
pool: PgPool, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<ShoppingCart> { ) -> Result<ShoppingCart> {
shopping_cart_set_state(msg, &pool).await
}
pub(crate) async fn shopping_cart_set_state<'e, E>(
msg: ShoppingCartSetState,
pool: E,
) -> Result<ShoppingCart>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE shopping_carts UPDATE shopping_carts
SET state = $2 SET state = $2, checkout_notes = $3
WHERE id = $1 WHERE id = $1
RETURNING id, buyer_id, payment_method, state RETURNING id, buyer_id, payment_method, state, checkout_notes
"#, "#,
) )
.bind(msg.id) .bind(msg.id)
.bind(msg.state) .bind(msg.state)
.bind(msg.checkout_notes)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| { .map_err(|e| {
@ -208,7 +201,7 @@ db_async_handler!(FindShoppingCart, find_shopping_cart, ShoppingCart);
pub(crate) async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result<ShoppingCart> { pub(crate) async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result<ShoppingCart> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, payment_method, state SELECT id, buyer_id, payment_method, state, checkout_notes
FROM shopping_carts FROM shopping_carts
WHERE id = $1 WHERE id = $1
"#, "#,
@ -245,7 +238,7 @@ INSERT INTO shopping_carts (buyer_id, state)
VALUES ($1, 'active') VALUES ($1, 'active')
ON CONFLICT ON CONFLICT
DO NOTHING DO NOTHING
RETURNING id, buyer_id, payment_method, state; RETURNING id, buyer_id, payment_method, state, checkout_notes
"#, "#,
) )
.bind(msg.buyer_id) .bind(msg.buyer_id)
@ -259,7 +252,7 @@ RETURNING id, buyer_id, payment_method, state;
}; };
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, buyer_id, payment_method, state SELECT id, buyer_id, payment_method, state, checkout_notes
FROM shopping_carts FROM shopping_carts
WHERE buyer_id = $1 AND state = 'active' WHERE buyer_id = $1 AND state = 'active'
"#, "#,

View File

@ -90,6 +90,7 @@ pub(crate) async fn create_order(
quantity_unit: item.quantity_unit, quantity_unit: item.quantity_unit,
}) })
.collect(), .collect(),
checkout_notes: cart.checkout_notes
}, },
Error::CreateAccountOrder, Error::CreateAccountOrder,
Error::DatabaseInternal Error::DatabaseInternal

View File

@ -231,6 +231,7 @@ pub(crate) async fn request_payment(
}) })
.collect(), .collect(),
shopping_cart_id: cart.id, shopping_cart_id: cart.id,
checkout_notes: cart.checkout_notes,
}, },
Error::CreateOrder Error::CreateOrder
); );

4
api/i18n.toml Normal file
View File

@ -0,0 +1,4 @@
available-locales = ["en", "pl"]
default-locale = "en"
fallback_language = "en"
load-path = "locales"

0
api/locales/en.yml Normal file
View File

0
api/locales/pl.yml Normal file
View File

View File

@ -59,6 +59,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
let fs_manager = fs_manager::FsManager::build(app_config.clone()) let fs_manager = fs_manager::FsManager::build(app_config.clone())
.await .await
.expect("Failed to initialize file system storage"); .expect("Failed to initialize file system storage");
let cart_manager = cart_manager::CartManager::new(db.clone()).start();
let addr = { let addr = {
let l = app_config.lock(); let l = app_config.lock();
let w = l.web(); let w = l.web();
@ -87,6 +88,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
.app_data(Data::new(payment_manager.clone())) .app_data(Data::new(payment_manager.clone()))
.app_data(Data::new(search_manager.clone())) .app_data(Data::new(search_manager.clone()))
.app_data(Data::new(fs_manager.clone())) .app_data(Data::new(fs_manager.clone()))
.app_data(Data::new(cart_manager.clone()))
.configure(routes::configure) .configure(routes::configure)
.service({ .service({
let l = app_config.lock(); let l = app_config.lock();

View File

@ -0,0 +1,2 @@
ALTER TABLE shopping_carts
ADD checkout_notes TEXT;

View File

@ -0,0 +1,2 @@
ALTER TABLE account_orders
ADD checkout_notes TEXT;

View File

@ -38,6 +38,7 @@ impl From<(Vec<crate::AccountOrder>, Vec<crate::OrderItem>)> for AccountOrders {
order_id, order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes,
}| { }| {
AccountOrder { AccountOrder {
id, id,
@ -45,6 +46,7 @@ impl From<(Vec<crate::AccountOrder>, Vec<crate::OrderItem>)> for AccountOrders {
status, status,
order_id, order_id,
items: items.drain_filter(|item| item.order_id == id).collect(), items: items.drain_filter(|item| item.order_id == id).collect(),
checkout_notes,
} }
}, },
) )
@ -63,6 +65,7 @@ impl From<(crate::AccountOrder, Vec<crate::OrderItem>)> for AccountOrder {
order_id, order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes,
}, },
mut items, mut items,
): (crate::AccountOrder, Vec<crate::OrderItem>), ): (crate::AccountOrder, Vec<crate::OrderItem>),
@ -73,6 +76,7 @@ impl From<(crate::AccountOrder, Vec<crate::OrderItem>)> for AccountOrder {
status, status,
order_id, order_id,
items: items.drain_filter(|item| item.order_id == id).collect(), items: items.drain_filter(|item| item.order_id == id).collect(),
checkout_notes,
} }
} }
} }
@ -85,6 +89,7 @@ pub struct AccountOrder {
pub status: crate::OrderStatus, pub status: crate::OrderStatus,
pub order_id: Option<crate::OrderId>, pub order_id: Option<crate::OrderId>,
pub items: Vec<crate::OrderItem>, pub items: Vec<crate::OrderItem>,
pub checkout_notes: Option<String>,
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
@ -125,6 +130,7 @@ pub struct ShoppingCart {
pub payment_method: PaymentMethod, pub payment_method: PaymentMethod,
pub state: ShoppingCartState, pub state: ShoppingCartState,
pub items: Vec<ShoppingCartItem>, pub items: Vec<ShoppingCartItem>,
pub checkout_notes: String,
} }
impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart { impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart {
@ -135,6 +141,7 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
buyer_id, buyer_id,
payment_method, payment_method,
state, state,
checkout_notes,
}, },
items, items,
): (crate::ShoppingCart, Vec<crate::ShoppingCartItem>), ): (crate::ShoppingCart, Vec<crate::ShoppingCartItem>),
@ -144,6 +151,7 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
buyer_id, buyer_id,
payment_method, payment_method,
state, state,
checkout_notes: checkout_notes.unwrap_or_default(),
items: items items: items
.into_iter() .into_iter()
.map( .map(

View File

@ -856,6 +856,7 @@ pub struct AccountOrder {
pub order_id: Option<OrderId>, pub order_id: Option<OrderId>,
pub order_ext_id: uuid::Uuid, pub order_ext_id: uuid::Uuid,
pub service_order_id: Option<String>, pub service_order_id: Option<String>,
pub checkout_notes: Option<String>,
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
@ -866,6 +867,7 @@ pub struct PublicAccountOrder {
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub status: OrderStatus, pub status: OrderStatus,
pub order_id: Option<OrderId>, pub order_id: Option<OrderId>,
pub checkout_notes: String,
} }
impl From<AccountOrder> for PublicAccountOrder { impl From<AccountOrder> for PublicAccountOrder {
@ -877,6 +879,7 @@ impl From<AccountOrder> for PublicAccountOrder {
order_id, order_id,
order_ext_id: _, order_ext_id: _,
service_order_id: _, service_order_id: _,
checkout_notes,
}: AccountOrder, }: AccountOrder,
) -> Self { ) -> Self {
Self { Self {
@ -884,6 +887,7 @@ impl From<AccountOrder> for PublicAccountOrder {
buyer_id, buyer_id,
status, status,
order_id, order_id,
checkout_notes: checkout_notes.unwrap_or_default(),
} }
} }
} }
@ -914,12 +918,13 @@ pub struct ShoppingCartId(pub RecordId);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::FromRow))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub struct ShoppingCart { pub struct ShoppingCart {
pub id: ShoppingCartId, pub id: ShoppingCartId,
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub payment_method: PaymentMethod, pub payment_method: PaymentMethod,
pub state: ShoppingCartState, pub state: ShoppingCartState,
pub checkout_notes: Option<String>,
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
@ -930,7 +935,7 @@ pub struct ShoppingCart {
pub struct ShoppingCartItemId(RecordId); pub struct ShoppingCartItemId(RecordId);
#[cfg_attr(feature = "db", derive(sqlx::FromRow))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub struct ShoppingCartItem { pub struct ShoppingCartItem {
pub id: ShoppingCartItemId, pub id: ShoppingCartItemId,
pub product_id: ProductId, pub product_id: ProductId,

View File

@ -25,7 +25,7 @@ wasm-bindgen = { version = "0.2.80", features = ["default"] }
web-sys = { version = "0.3.57", features = ["Navigator"] } web-sys = { version = "0.3.57", features = ["Navigator"] }
js-sys = { version = "0.3.57", features = [] } js-sys = { version = "0.3.57", features = [] }
indexmap = { version = "1", features = ["serde-1"] } indexmap = { version = "1", default-features = false, features = ["serde-1", "std"] }
rusty-money = { version = "0.4.1", features = ["iso"] } rusty-money = { version = "0.4.1", features = ["iso"] }

View File

@ -80,6 +80,7 @@ mod summary_left {
use seed::*; use seed::*;
use crate::pages::public::shopping_cart::ShoppingCartPage; use crate::pages::public::shopping_cart::ShoppingCartPage;
use crate::shopping_cart::CartMsg;
pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> { pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
div![ div![
@ -94,7 +95,16 @@ mod summary_left {
] ]
], ],
div![C!["p-4"], p![C!["mb-4 italic"], model.i18n.t("If you have some information for the seller you can leave them in the box below")]], div![C!["p-4"], p![C!["mb-4 italic"], model.i18n.t("If you have some information for the seller you can leave them in the box below")]],
textarea![C!["w-full h-24 p-2 bg-gray-100 rounded border-none"]] textarea![
C!["w-full h-24 p-2 bg-gray-100 rounded border-none"],
ev(Ev::Change, move |ev| {
ev.stop_propagation();
let target = ev.target()?;
let input = seed::to_textarea(&target);
Some(crate::Msg::from(CartMsg::ChangeNotes(input.value())))
}),
model.cart.checkout_notes.as_str()
]
] ]
} }

View File

@ -23,6 +23,7 @@ pub enum CartMsg {
/// Send current non-empty cart to server /// Send current non-empty cart to server
Sync, Sync,
SyncResult(NetRes<model::api::UpdateCartOutput>), SyncResult(NetRes<model::api::UpdateCartOutput>),
ChangeNotes(String),
} }
impl From<CartMsg> for Msg { impl From<CartMsg> for Msg {
@ -44,6 +45,8 @@ pub struct Item {
pub struct ShoppingCart { pub struct ShoppingCart {
pub cart_id: Option<model::ShoppingCartId>, pub cart_id: Option<model::ShoppingCartId>,
pub items: Items, pub items: Items,
#[serde(default)]
pub checkout_notes: String,
#[serde(skip)] #[serde(skip)]
pub hover: bool, pub hover: bool,
} }
@ -70,6 +73,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
entry.quantity_unit = quantity_unit; entry.quantity_unit = quantity_unit;
} }
store_local(&model.cart); store_local(&model.cart);
sync_cart(model, orders);
} }
CartMsg::ModifyItem { CartMsg::ModifyItem {
product_id, product_id,
@ -89,10 +93,12 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
entry.quantity_unit = quantity_unit; entry.quantity_unit = quantity_unit;
} }
store_local(&model.cart); store_local(&model.cart);
sync_cart(model, orders);
} }
CartMsg::Remove(product_id) => { CartMsg::Remove(product_id) => {
model.cart.items.remove(&product_id); model.cart.items.remove(&product_id);
store_local(&model.cart); store_local(&model.cart);
sync_cart(model, orders);
} }
CartMsg::Hover => { CartMsg::Hover => {
model.cart.hover = true; model.cart.hover = true;
@ -100,18 +106,30 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
CartMsg::Leave => { CartMsg::Leave => {
model.cart.hover = false; model.cart.hover = false;
} }
CartMsg::Sync => { CartMsg::Sync => sync_cart(model, orders),
if let Some(access_token) = model.shared.access_token.as_ref().cloned() {
let items: Vec<Item> = 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,
))
});
}
}
CartMsg::SyncResult(NetRes::Success(cart)) => { CartMsg::SyncResult(NetRes::Success(cart)) => {
// cart.items 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)) => { CartMsg::SyncResult(NetRes::Error(failure)) => {
for msg in failure.errors { for msg in failure.errors {
@ -121,6 +139,21 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
CartMsg::SyncResult(NetRes::Http(_cart)) => { CartMsg::SyncResult(NetRes::Http(_cart)) => {
orders.send_msg(NotificationMsg::Error("Unable to sync cart".into()).into()); 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<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();
orders.perform_cmd(async {
crate::Msg::from(CartMsg::SyncResult(
crate::api::public::update_cart(access_token, items).await,
))
});
} }
} }