Shopping cart
This commit is contained in:
parent
f0d01a9735
commit
c9eb49410b
@ -1,8 +1,10 @@
|
|||||||
pub mod api_v1;
|
pub mod api_v1;
|
||||||
|
|
||||||
use actix_web::web::{Path, ServiceConfig};
|
use actix_web::web::{Data, Json, Path, ServiceConfig};
|
||||||
use actix_web::{get, HttpResponse};
|
use actix_web::{get, HttpResponse};
|
||||||
pub use api_v1::{Error as V1Error, ShoppingCartError as V1ShoppingCartError};
|
pub use api_v1::{Error as V1Error, ShoppingCartError as V1ShoppingCartError};
|
||||||
|
use config::SharedAppConfig;
|
||||||
|
use model::api::Config;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! public_send_db {
|
macro_rules! public_send_db {
|
||||||
@ -57,6 +59,26 @@ async fn landing() -> HttpResponse {
|
|||||||
HttpResponse::NotImplemented().body("")
|
HttpResponse::NotImplemented().body("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/config.json")]
|
||||||
|
async fn client_config(config: Data<SharedAppConfig>) -> Json<model::api::Config> {
|
||||||
|
let (optional_payment, currency) = {
|
||||||
|
let lock = config.lock();
|
||||||
|
let p = lock.payment();
|
||||||
|
(p.optional_payment(), p.currency())
|
||||||
|
};
|
||||||
|
Json(Config {
|
||||||
|
coupons: false,
|
||||||
|
pay_methods: match optional_payment {
|
||||||
|
true => vec![
|
||||||
|
model::PaymentMethod::PayU,
|
||||||
|
model::PaymentMethod::PaymentOnTheSpot,
|
||||||
|
],
|
||||||
|
false => vec![model::PaymentMethod::PayU],
|
||||||
|
},
|
||||||
|
currency,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/pay-on-site")]
|
#[get("/pay-on-site")]
|
||||||
async fn pay_on_site() -> HttpResponse {
|
async fn pay_on_site() -> HttpResponse {
|
||||||
HttpResponse::Ok().body("<h1>Pay on Site</h1>")
|
HttpResponse::Ok().body("<h1>Pay on Site</h1>")
|
||||||
@ -93,5 +115,6 @@ pub fn configure(config: &mut ServiceConfig) {
|
|||||||
config
|
config
|
||||||
.service(landing)
|
.service(landing)
|
||||||
.service(svg)
|
.service(svg)
|
||||||
|
.service(client_config)
|
||||||
.configure(api_v1::configure);
|
.configure(api_v1::configure);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,13 @@ pub struct Failure {
|
|||||||
pub errors: Vec<String>,
|
pub errors: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub pay_methods: Vec<PaymentMethod>,
|
||||||
|
pub coupons: bool,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use seed::fetch::{Header, Method, Request};
|
use seed::fetch::{Method, Request};
|
||||||
|
|
||||||
use crate::api::{perform, NetRes};
|
use crate::api::{perform, NetRes};
|
||||||
|
|
||||||
pub async fn sign_in(identity: String, password: model::Password) -> NetRes<model::Account> {
|
pub async fn sign_in(
|
||||||
|
identity: String,
|
||||||
|
password: model::Password,
|
||||||
|
) -> NetRes<model::api::SessionOutput> {
|
||||||
use model::api::admin::SignInInput;
|
use model::api::admin::SignInInput;
|
||||||
|
|
||||||
let input = if identity.contains('@') {
|
let input = if identity.contains('@') {
|
||||||
|
@ -3,6 +3,10 @@ use seed::fetch::{Header, Method, Request};
|
|||||||
|
|
||||||
use crate::api::perform;
|
use crate::api::perform;
|
||||||
|
|
||||||
|
pub async fn config() -> super::NetRes<model::api::Config> {
|
||||||
|
perform(Request::new("/config.json").method(Method::Get)).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_products() -> super::NetRes<model::api::Products> {
|
pub async fn fetch_products() -> super::NetRes<model::api::Products> {
|
||||||
perform(Request::new("/api/v1/products").method(Method::Get)).await
|
perform(Request::new("/api/v1/products").method(Method::Get)).await
|
||||||
}
|
}
|
||||||
|
@ -47,5 +47,22 @@ pub fn define(i18n: &mut I18n) {
|
|||||||
.define("Quantity", "Ilość")
|
.define("Quantity", "Ilość")
|
||||||
.define("Unit price", "Cena jednostkowa")
|
.define("Unit price", "Cena jednostkowa")
|
||||||
.define("Total price", "Cena łączna")
|
.define("Total price", "Cena łączna")
|
||||||
.define("Delivery", "Sposób dostawy");
|
.define("Delivery", "Sposób dostawy")
|
||||||
|
.define("Coupon Code", "Kod rabatowy")
|
||||||
|
.define(
|
||||||
|
"If you have a coupon code, please enter it in the box below",
|
||||||
|
"Jeśli posiadasz kod kuponu, wpisz go w poniższe pole.",
|
||||||
|
)
|
||||||
|
.define("Apply coupon", "Zastosuj kupon")
|
||||||
|
.define("Instruction for seller", "Instrukcje dla sprzedawcy")
|
||||||
|
.define(
|
||||||
|
"If you have some information for the seller you can leave them in the box below",
|
||||||
|
"Jeśli masz jakieś informacje dla sprzedawcy, możesz je zostawić w poniższym polu",
|
||||||
|
)
|
||||||
|
.define("Order Details", "Szczegóły zamówienia")
|
||||||
|
.define("Shipping and additional costs are calculated based on values you have entered", "Koszty wysyłki i koszty dodatkowe są obliczane na podstawie wprowadzonych przez użytkownika wartości")
|
||||||
|
.define("Subtotal", "Suma częściowa")
|
||||||
|
.define("New Subtotal", "Łącznie")
|
||||||
|
.define("Total", "Łącznie")
|
||||||
|
.define("Proceed to checkout", "Przejdź do kasy");
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ pub mod shopping_cart;
|
|||||||
use seed::empty;
|
use seed::empty;
|
||||||
use seed::prelude::*;
|
use seed::prelude::*;
|
||||||
|
|
||||||
|
use crate::api::NetRes;
|
||||||
use crate::i18n::I18n;
|
use crate::i18n::I18n;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::pages::{AdminPage, Msg, Page, PublicPage};
|
use crate::pages::{AdminPage, Msg, Page, PublicPage};
|
||||||
@ -105,7 +106,9 @@ macro_rules! fetch_page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||||
orders.subscribe(Msg::UrlChanged);
|
orders
|
||||||
|
.subscribe(Msg::UrlChanged)
|
||||||
|
.perform_cmd(async { Msg::Config(crate::api::public::config().await) });
|
||||||
|
|
||||||
let mut model = Model {
|
let mut model = Model {
|
||||||
url: url.clone().set_path(&[] as &[&str]),
|
url: url.clone().set_path(&[] as &[&str]),
|
||||||
@ -120,6 +123,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
i18n: I18n::load(),
|
i18n: I18n::load(),
|
||||||
cart: Default::default(),
|
cart: Default::default(),
|
||||||
|
|
||||||
|
config: model::Config::default(),
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
debug_modal: false,
|
debug_modal: false,
|
||||||
};
|
};
|
||||||
@ -144,6 +148,11 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Msg::NoOp => {
|
Msg::NoOp => {
|
||||||
orders.skip();
|
orders.skip();
|
||||||
}
|
}
|
||||||
|
Msg::Config(res) => {
|
||||||
|
if let NetRes::Success(config) = res {
|
||||||
|
model.config = config.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
Msg::Shared(msg) => {
|
Msg::Shared(msg) => {
|
||||||
shared::update(msg, model, orders);
|
shared::update(msg, model, orders);
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,45 @@ pub struct Model {
|
|||||||
pub shared: crate::shared::Model,
|
pub shared: crate::shared::Model,
|
||||||
pub i18n: I18n,
|
pub i18n: I18n,
|
||||||
pub cart: shopping_cart::ShoppingCart,
|
pub cart: shopping_cart::ShoppingCart,
|
||||||
|
pub config: Config,
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub debug_modal: bool,
|
pub debug_modal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub pay_methods: Vec<::model::PaymentMethod>,
|
||||||
|
pub coupons: bool,
|
||||||
|
pub currency: &'static rusty_money::iso::Currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pay_methods: vec![],
|
||||||
|
coupons: false,
|
||||||
|
currency: rusty_money::iso::PLN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::model::api::Config> for Config {
|
||||||
|
fn from(
|
||||||
|
model::api::Config {
|
||||||
|
pay_methods,
|
||||||
|
coupons,
|
||||||
|
currency,
|
||||||
|
}: model::api::Config,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
pay_methods,
|
||||||
|
coupons,
|
||||||
|
currency: rusty_money::iso::find(¤cy).unwrap_or(rusty_money::iso::PLN),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Products {
|
pub struct Products {
|
||||||
pub categories: Vec<model::api::Category>,
|
pub categories: Vec<model::api::Category>,
|
||||||
|
@ -4,12 +4,13 @@ pub mod public;
|
|||||||
use seed::app::{subs, Orders};
|
use seed::app::{subs, Orders};
|
||||||
use seed::{struct_urls, Url};
|
use seed::{struct_urls, Url};
|
||||||
|
|
||||||
use crate::shared;
|
use crate::{shared, NetRes};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
NoOp,
|
NoOp,
|
||||||
|
Config(NetRes<::model::api::Config>),
|
||||||
Public(public::Msg),
|
Public(public::Msg),
|
||||||
Admin(admin::Msg),
|
Admin(admin::Msg),
|
||||||
UrlChanged(subs::UrlChanged),
|
UrlChanged(subs::UrlChanged),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod checkout;
|
||||||
pub mod listing;
|
pub mod listing;
|
||||||
pub mod product;
|
pub mod product;
|
||||||
pub mod shopping_cart;
|
pub mod shopping_cart;
|
||||||
|
0
web/src/pages/public/checkout.rs
Normal file
0
web/src/pages/public/checkout.rs
Normal file
@ -73,7 +73,8 @@ pub fn update(msg: ListingMsg, model: &mut ListingPage, orders: &mut impl Orders
|
|||||||
orders.skip().perform_cmd({
|
orders.skip().perform_cmd({
|
||||||
async {
|
async {
|
||||||
crate::Msg::Public(
|
crate::Msg::Public(
|
||||||
ListingMsg::ProductsFetched(crate::api::public::fetch_products().await).into(),
|
ListingMsg::ProductsFetched(crate::api::public::fetch_products().await)
|
||||||
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -85,7 +86,8 @@ pub fn update(msg: ListingMsg, model: &mut ListingPage, orders: &mut impl Orders
|
|||||||
.filter_product_ids(|product| filter_product(model, product));
|
.filter_product_ids(|product| filter_product(model, product));
|
||||||
model.visible_products = ids;
|
model.visible_products = ids;
|
||||||
}
|
}
|
||||||
ListingMsg::ProductsFetched(NetRes::Error(_)) | ListingMsg::ProductsFetched(NetRes::Http(_)) => {
|
ListingMsg::ProductsFetched(NetRes::Error(_))
|
||||||
|
| ListingMsg::ProductsFetched(NetRes::Http(_)) => {
|
||||||
model.errors.push("Failed to load products".into());
|
model.errors.push("Failed to load products".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,9 +122,9 @@ pub fn view(model: &crate::Model, page: &ListingPage) -> Node<crate::Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn product(model: &crate::Model, product: &model::api::Product) -> Node<crate::Msg> {
|
fn product(model: &crate::Model, product: &model::api::Product) -> Node<crate::Msg> {
|
||||||
use rusty_money::{iso, Money};
|
use rusty_money::Money;
|
||||||
|
|
||||||
let price = Money::from_minor(**product.price as i64, iso::PLN).to_string();
|
let price = Money::from_minor(**product.price as i64, model.config.currency).to_string();
|
||||||
let _description = product.short_description.as_str();
|
let _description = product.short_description.as_str();
|
||||||
let name = product.name.as_str();
|
let name = product.name.as_str();
|
||||||
let img = product
|
let img = product
|
||||||
|
@ -52,8 +52,13 @@ pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
|||||||
C!["flex justify-center my-6"],
|
C!["flex justify-center my-6"],
|
||||||
div![
|
div![
|
||||||
C!["flex flex-col w-full p-8 text-gray-800 bg-white shadow-lg pin-r pin-y md:w-4/5 lg:w-4/5"],
|
C!["flex flex-col w-full p-8 text-gray-800 bg-white shadow-lg pin-r pin-y md:w-4/5 lg:w-4/5"],
|
||||||
products(model, page)
|
div![
|
||||||
]
|
C!["flex-1"],
|
||||||
|
products(model, page),
|
||||||
|
div![C!["pb-6 mt-6"]],
|
||||||
|
summary(model, page),
|
||||||
|
]
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
div![
|
div![
|
||||||
@ -62,18 +67,284 @@ pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn products(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
fn summary(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
div![
|
div![
|
||||||
C!["flex-1"],
|
C!["my-4 mt-6 -mx-2 lg:flex"],
|
||||||
table![
|
summary_left(model, page),
|
||||||
C!["w-full text-sm lg:text-base"],
|
summary_right::view(model, page)
|
||||||
attrs!["cellspacing" => 0],
|
]
|
||||||
products_head(model),
|
}
|
||||||
products_body(model, page),
|
|
||||||
|
fn summary_left(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
|
div![
|
||||||
|
C!["lg:px-2 lg:w-1/2"],
|
||||||
|
IF![model.config.coupons => div![C!["p-4 bg-gray-100 rounded-full"], h1![C!["ml-2 font-bold uppercase"], model.i18n.t("Coupon Code")]]],
|
||||||
|
IF![model.config.coupons => coupon_form(model, page)],
|
||||||
|
div![
|
||||||
|
C!["p-4 bg-gray-100 rounded-full", IF![model.config.coupons => "mt-6"]],
|
||||||
|
div![
|
||||||
|
C!["ml-2 font-bold uppercase"],
|
||||||
|
model.i18n.t("Instruction for seller")
|
||||||
|
]
|
||||||
|
],
|
||||||
|
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"]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coupon_form(model: &crate::Model, _page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
|
div![
|
||||||
|
C!["p-4"],
|
||||||
|
p![
|
||||||
|
C!["mb-4 italic"],
|
||||||
|
model
|
||||||
|
.i18n
|
||||||
|
.t("If you have a coupon code, please enter it in the box below")
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["justify-center md:flex"],
|
||||||
|
form![
|
||||||
|
div![
|
||||||
|
C!["flex items-center w-full h-13 pl-3 bg-white bg-gray-100 border rounded-full"],
|
||||||
|
input![C!["w-full bg-gray-100 outline-none appearance-none focus:outline-none active:outline-none"]],
|
||||||
|
button![
|
||||||
|
C!["text-sm flex items-center px-3 py-1 text-white bg-gray-800 rounded-full outline-none md:px-4 hover:bg-gray-700 focus:outline-none active:outline-none"],
|
||||||
|
gift_icon(),
|
||||||
|
span![C!["font-medium"], model.i18n.t("Apply coupon")]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gift_icon() -> Node<crate::Msg> {
|
||||||
|
svg![
|
||||||
|
attrs![
|
||||||
|
"aria-hidden" => "true",
|
||||||
|
"data-prefix" => "fas",
|
||||||
|
"data-icon" => "gift",
|
||||||
|
"class" => "w-8",
|
||||||
|
"xmlns" => "http://www.w3.org/2000/svg",
|
||||||
|
"viewBox" => "0 0 512 512"
|
||||||
|
],
|
||||||
|
path![attrs![
|
||||||
|
"fill" => "currentColor",
|
||||||
|
"d" => "M32 448c0 17.7 14.3 32 32 32h160V320H32v128zm256 32h160c17.7 0 32-14.3 32-32V320H288v160zm192-320h-42.1c6.2-12.1 10.1-25.5 10.1-40 0-48.5-39.5-88-88-88-41.6 0-68.5 21.3-103 68.3-34.5-47-61.4-68.3-103-68.3-48.5 0-88 39.5-88 88 0 14.5 3.8 27.9 10.1 40H32c-17.7 0-32 14.3-32 32v80c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-80c0-17.7-14.3-32-32-32zm-326.1 0c-22.1 0-40-17.9-40-40s17.9-40 40-40c19.9 0 34.6 3.3 86.1 80h-86.1zm206.1 0h-86.1c51.4-76.5 65.7-80 86.1-80 22.1 0 40 17.9 40 40s-17.9 40-40 40z"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mod summary_right {
|
||||||
|
use rusty_money::Money;
|
||||||
|
use seed::prelude::*;
|
||||||
|
use seed::*;
|
||||||
|
|
||||||
|
use crate::pages::public::shopping_cart::ShoppingCartPage;
|
||||||
|
use crate::pages::Urls;
|
||||||
|
|
||||||
|
pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
|
let subtotal_value = model
|
||||||
|
.cart
|
||||||
|
.items
|
||||||
|
.values()
|
||||||
|
.filter_map(|item: &crate::shopping_cart::Item| {
|
||||||
|
page.products
|
||||||
|
.products
|
||||||
|
.get(&item.product_id)
|
||||||
|
.map(|product| (item, product))
|
||||||
|
})
|
||||||
|
.map(
|
||||||
|
|(item, product): (&crate::shopping_cart::Item, &model::api::Product)| {
|
||||||
|
**(product.price * item.quantity)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.sum::<i32>() as i64;
|
||||||
|
div![
|
||||||
|
C!["lg:px-2 lg:w-1/2"],
|
||||||
|
div![
|
||||||
|
C!["p-4 bg-gray-100 rounded-full"],
|
||||||
|
h1![
|
||||||
|
C!["ml-2 font-bold uppercase"],
|
||||||
|
model.i18n.t("Order Details")
|
||||||
|
]
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["p-4"],
|
||||||
|
p![C!["mb-6 italic"], model.i18n.t("Shipping and additional costs are calculated based on values you have entered")],
|
||||||
|
subtotal(model, page, subtotal_value),
|
||||||
|
coupon_subtotal(model, page),
|
||||||
|
new_subtotal(model, page, subtotal_value),
|
||||||
|
total(model, page, subtotal_value),
|
||||||
|
checkout(model),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkout(model: &crate::Model) -> Node<crate::Msg> {
|
||||||
|
a![
|
||||||
|
attrs![At::Href => Urls::new(&model.url).checkout()],
|
||||||
|
button![
|
||||||
|
C!["flex justify-center w-full px-10 py-3 mt-6 font-medium text-white uppercase bg-gray-800 rounded-full shadow item-center hover:bg-gray-700 focus:shadow-outline focus:outline-none"],
|
||||||
|
cart_icon(),
|
||||||
|
span![
|
||||||
|
C!["ml-2 mt-5px"],
|
||||||
|
model.i18n.t("Proceed to checkout")
|
||||||
|
],
|
||||||
|
ev(Ev::Click, move |ev| {
|
||||||
|
ev.prevent_default()
|
||||||
|
})
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subtotal(
|
||||||
|
model: &crate::Model,
|
||||||
|
_page: &ShoppingCartPage,
|
||||||
|
subtotal_value: i64,
|
||||||
|
) -> Node<crate::Msg> {
|
||||||
|
let subtotal = Money::from_minor(subtotal_value, model.config.currency);
|
||||||
|
|
||||||
|
div![
|
||||||
|
C!["flex justify-between border-b"],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 text-lg lg:text-xl font-bold text-center text-gray-800"],
|
||||||
|
model.i18n.t("Subtotal")
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 lg:text-lg font-bold text-center text-gray-900"],
|
||||||
|
subtotal.to_string()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coupon_subtotal(model: &crate::Model, _page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
|
if !model.config.coupons {
|
||||||
|
return empty![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO! Coupons
|
||||||
|
let coupon = "90off";
|
||||||
|
let coupon_discount = 1654;
|
||||||
|
|
||||||
|
div![
|
||||||
|
C!["flex justify-between pt-4 border-b"],
|
||||||
|
div![
|
||||||
|
C!["flex lg:px-4 lg:py-2 m-2 text-lg lg:text-xl font-bold text-gray-800"],
|
||||||
|
form![button![C!["mr-2 mt-1 lg:mt-2"], trash_icon()]],
|
||||||
|
model.i18n.t("Coupon"),
|
||||||
|
" ",
|
||||||
|
format!("{:?}", coupon)
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 lg:text-lg font-bold text-center text-green-700"],
|
||||||
|
format!(
|
||||||
|
"-{}",
|
||||||
|
Money::from_minor(coupon_discount, model.config.currency)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_subtotal(
|
||||||
|
model: &crate::Model,
|
||||||
|
_page: &ShoppingCartPage,
|
||||||
|
subtotal_value: i64,
|
||||||
|
) -> Node<crate::Msg> {
|
||||||
|
// TODO! Coupons and Delivery
|
||||||
|
let coupon_discount = 0;
|
||||||
|
|
||||||
|
if !model.config.coupons {
|
||||||
|
return empty![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_subtotal =
|
||||||
|
Money::from_minor(subtotal_value - coupon_discount, model.config.currency);
|
||||||
|
|
||||||
|
div![
|
||||||
|
C!["flex justify-between pt-4 border-b"],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 text-lg lg:text-xl font-bold text-center text-gray-800"],
|
||||||
|
model.i18n.t("New Subtotal")
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 lg:text-lg font-bold text-center text-gray-900"],
|
||||||
|
new_subtotal.to_string()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total(
|
||||||
|
model: &crate::Model,
|
||||||
|
_page: &ShoppingCartPage,
|
||||||
|
subtotal_value: i64,
|
||||||
|
) -> Node<crate::Msg> {
|
||||||
|
// TODO! Coupons and Delivery
|
||||||
|
let coupon_discount = 0;
|
||||||
|
let delivery_cost = 0;
|
||||||
|
|
||||||
|
let new_subtotal = Money::from_minor(
|
||||||
|
subtotal_value - coupon_discount + delivery_cost,
|
||||||
|
model.config.currency,
|
||||||
|
);
|
||||||
|
|
||||||
|
div![
|
||||||
|
C!["flex justify-between pt-4 border-b"],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 text-lg lg:text-xl font-bold text-center text-gray-800"],
|
||||||
|
model.i18n.t("Total")
|
||||||
|
],
|
||||||
|
div![
|
||||||
|
C!["lg:px-4 lg:py-2 m-2 lg:text-lg font-bold text-center text-gray-900"],
|
||||||
|
new_subtotal.to_string()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trash_icon() -> Node<crate::Msg> {
|
||||||
|
svg![
|
||||||
|
attrs![
|
||||||
|
"aria-hidden" => "true",
|
||||||
|
"data-prefix" => "far",
|
||||||
|
"data-icon" => "trash-alt",
|
||||||
|
"class" => "w-4 text-red-600 hover:text-red-800",
|
||||||
|
"xmlns" => "http://www.w3.org/2000/svg",
|
||||||
|
"viewBox" => "0 0 448 512"
|
||||||
|
],
|
||||||
|
path![attrs![
|
||||||
|
"fill" => "currentColor",
|
||||||
|
"d" => "M268 416h24a12 12 0 0012-12V188a12 12 0 00-12-12h-24a12 12 0 00-12 12v216a12 12 0 0012 12zM432 80h-82.41l-34-56.7A48 48 0 00274.41 0H173.59a48 48 0 00-41.16 23.3L98.41 80H16A16 16 0 000 96v16a16 16 0 0016 16h16v336a48 48 0 0048 48h288a48 48 0 0048-48V128h16a16 16 0 0016-16V96a16 16 0 00-16-16zM171.84 50.91A6 6 0 01177 48h94a6 6 0 015.15 2.91L293.61 80H154.39zM368 464H80V128h288zm-212-48h24a12 12 0 0012-12V188a12 12 0 00-12-12h-24a12 12 0 00-12 12v216a12 12 0 0012 12z"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cart_icon() -> Node<crate::Msg> {
|
||||||
|
svg![
|
||||||
|
attrs![
|
||||||
|
"aria-hidden" => "true",
|
||||||
|
"data-prefix" => "far",
|
||||||
|
"data-icon" => "credit-card",
|
||||||
|
"class" => "w-8",
|
||||||
|
"xmlns" => "http://www.w3.org/2000/svg",
|
||||||
|
"viewBox" => "0 0 576 512"
|
||||||
|
],
|
||||||
|
path![attrs![
|
||||||
|
"fill" => "currentColor",
|
||||||
|
"d" => "M527.9 32H48.1C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48.1 48h479.8c26.6 0 48.1-21.5 48.1-48V80c0-26.5-21.5-48-48.1-48zM54.1 80h467.8c3.3 0 6 2.7 6 6v42H48.1V86c0-3.3 2.7-6 6-6zm467.8 352H54.1c-3.3 0-6-2.7-6-6V256h479.8v170c0 3.3-2.7 6-6 6zM192 332v40c0 6.6-5.4 12-12 12h-72c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h72c6.6 0 12 5.4 12 12zm192 0v40c0 6.6-5.4 12-12 12H236c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h136c6.6 0 12 5.4 12 12z"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn products(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||||
|
table![
|
||||||
|
C!["w-full text-sm lg:text-base"],
|
||||||
|
attrs!["cellspacing" => 0],
|
||||||
|
products_head(model),
|
||||||
|
products_body(model, page),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
fn products_head(model: &crate::Model) -> Node<crate::Msg> {
|
fn products_head(model: &crate::Model) -> Node<crate::Msg> {
|
||||||
thead![tr![
|
thead![tr![
|
||||||
C!["h-12 uppercase"],
|
C!["h-12 uppercase"],
|
||||||
@ -120,7 +391,6 @@ fn item_view(
|
|||||||
item: &crate::shopping_cart::Item,
|
item: &crate::shopping_cart::Item,
|
||||||
product: &model::api::Product,
|
product: &model::api::Product,
|
||||||
) -> Node<crate::Msg> {
|
) -> Node<crate::Msg> {
|
||||||
use rusty_money::iso::PLN;
|
|
||||||
use rusty_money::Money;
|
use rusty_money::Money;
|
||||||
|
|
||||||
let img = product
|
let img = product
|
||||||
@ -139,7 +409,7 @@ fn item_view(
|
|||||||
td![
|
td![
|
||||||
C!["hidden pb-4 md:table-cell"],
|
C!["hidden pb-4 md:table-cell"],
|
||||||
a![
|
a![
|
||||||
attrs![At::Href => product_url.clone()],
|
attrs![At::Href => &product_url],
|
||||||
img![attrs![
|
img![attrs![
|
||||||
At::Src => img,
|
At::Src => img,
|
||||||
At::Class => "w-20 rounded",
|
At::Class => "w-20 rounded",
|
||||||
@ -148,7 +418,7 @@ fn item_view(
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
td![a![
|
td![a![
|
||||||
attrs![At::Href => product_url.clone()],
|
attrs![At::Href => &product_url],
|
||||||
p![C!["mb-2 md:ml-4"], product.name.as_str()],
|
p![C!["mb-2 md:ml-4"], product.name.as_str()],
|
||||||
form![
|
form![
|
||||||
ev(Ev::Submit, move |ev| {
|
ev(Ev::Submit, move |ev| {
|
||||||
@ -177,7 +447,7 @@ fn item_view(
|
|||||||
attrs![
|
attrs![
|
||||||
At::Type => "number",
|
At::Type => "number",
|
||||||
At::Value => **item.quantity,
|
At::Value => **item.quantity,
|
||||||
At::Class => "w-full font-semibold text-center text-gray-700 bg-gray-200 outline-none focus:outline-none hover:text-black focus:text-black"
|
At::Class => "w-full font-semibold text-center text-gray-700 bg-gray-200 outline-none focus:outline-none hover:text-black focus:text-black border-none"
|
||||||
],
|
],
|
||||||
ev(Ev::Change, move |ev| {
|
ev(Ev::Change, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
@ -200,14 +470,18 @@ fn item_view(
|
|||||||
C!["hidden text-right md:table-cell"],
|
C!["hidden text-right md:table-cell"],
|
||||||
span![
|
span![
|
||||||
C!["text-sm lg:text-base font-medium"],
|
C!["text-sm lg:text-base font-medium"],
|
||||||
Money::from_minor(**product.price as i64, PLN).to_string()
|
Money::from_minor(**product.price as i64, model.config.currency).to_string()
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
td![
|
td![
|
||||||
C!["text-right"],
|
C!["text-right"],
|
||||||
span![
|
span![
|
||||||
C!["text-sm lg:text-base font-medium"],
|
C!["text-sm lg:text-base font-medium"],
|
||||||
Money::from_minor(**(product.price * item.quantity) as i64, PLN).to_string()
|
Money::from_minor(
|
||||||
|
**(product.price * item.quantity) as i64,
|
||||||
|
model.config.currency
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -120,6 +120,7 @@ pub fn update(msg: SessionMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
access_token,
|
access_token,
|
||||||
refresh_token,
|
refresh_token,
|
||||||
exp,
|
exp,
|
||||||
|
role: _,
|
||||||
})) => {
|
})) => {
|
||||||
orders
|
orders
|
||||||
.skip()
|
.skip()
|
||||||
|
@ -77,6 +77,7 @@ fn handle_auth_pair(
|
|||||||
access_token,
|
access_token,
|
||||||
refresh_token,
|
refresh_token,
|
||||||
exp,
|
exp,
|
||||||
|
role: _,
|
||||||
} = pair;
|
} = pair;
|
||||||
|
|
||||||
model.access_token = Some(access_token);
|
model.access_token = Some(access_token);
|
||||||
|
@ -149,7 +149,7 @@ pub mod cart_dropdown {
|
|||||||
|
|
||||||
let price = rusty_money::Money::from_minor(
|
let price = rusty_money::Money::from_minor(
|
||||||
**(product.price * item.quantity) as i64,
|
**(product.price * item.quantity) as i64,
|
||||||
rusty_money::iso::PLN,
|
model.config.currency,
|
||||||
)
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
div![
|
div![
|
||||||
@ -204,7 +204,7 @@ pub mod cart_dropdown {
|
|||||||
let sum: i32 = filter_products!(model, products)
|
let sum: i32 = filter_products!(model, products)
|
||||||
.map(|(item, product): (&Item, &model::api::Product)| **item.quantity * **product.price)
|
.map(|(item, product): (&Item, &model::api::Product)| **item.quantity * **product.price)
|
||||||
.sum();
|
.sum();
|
||||||
let sum = rusty_money::Money::from_minor(sum as i64, rusty_money::iso::PLN);
|
let sum = rusty_money::Money::from_minor(sum as i64, model.config.currency);
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["p-4 justify-center flex"],
|
C!["p-4 justify-center flex"],
|
||||||
|
Loading…
Reference in New Issue
Block a user