Admin landing, translations, shopping cart events
This commit is contained in:
parent
4db6cf7327
commit
d5e675b6dc
@ -292,6 +292,12 @@ impl ops::Mul<Quantity> for Price {
|
||||
#[serde(transparent)]
|
||||
pub struct Quantity(NonNegative);
|
||||
|
||||
impl Quantity {
|
||||
pub fn from_u32(v: u32) -> Self {
|
||||
Self(NonNegative(v.try_into().unwrap_or_default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for Quantity {
|
||||
type Output = Self;
|
||||
|
||||
|
@ -40,5 +40,12 @@ pub fn define(i18n: &mut I18n) {
|
||||
.define("Home", "Home")
|
||||
.define("Account", "Profil")
|
||||
.define("Shopping cart", "Koszyk")
|
||||
.define("Qty:", "Ilość:");
|
||||
.define("Qty:", "Ilość:")
|
||||
// shopping cart
|
||||
.define("(Remove item)", "(Usuń)")
|
||||
.define("Product", "Produkt")
|
||||
.define("Quantity", "Ilość")
|
||||
.define("Unit price", "Cena jednostkowa")
|
||||
.define("Total price", "Cena łączna")
|
||||
.define("Delivery", "Sposób dostawy");
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use seed::prelude::*;
|
||||
|
||||
use crate::i18n::I18n;
|
||||
use crate::model::Model;
|
||||
use crate::pages::{Msg, Page, PublicPage};
|
||||
use crate::pages::{AdminPage, Msg, Page, PublicPage};
|
||||
use crate::session::SessionMsg;
|
||||
|
||||
#[macro_export]
|
||||
@ -30,6 +30,16 @@ macro_rules! fetch_page {
|
||||
_ => return $ret,
|
||||
}
|
||||
}};
|
||||
(admin $model: expr, $page: ident, $ret: expr) => {{
|
||||
let p = match &mut $model.page {
|
||||
crate::pages::Page::Admin(p) => p,
|
||||
_ => return $ret,
|
||||
};
|
||||
match p {
|
||||
crate::pages::AdminPage::$page(p) => p,
|
||||
_ => return $ret,
|
||||
}
|
||||
}};
|
||||
(public $model: expr, $page: ident) => {{
|
||||
let p = match &mut $model.page {
|
||||
crate::pages::Page::Public(p) => p,
|
||||
@ -40,6 +50,16 @@ macro_rules! fetch_page {
|
||||
_ => return,
|
||||
}
|
||||
}};
|
||||
(admin $model: expr, $page: ident) => {{
|
||||
let p = match &mut $model.page {
|
||||
crate::pages::Page::Admin(p) => p,
|
||||
_ => return,
|
||||
};
|
||||
match p {
|
||||
crate::pages::AdminPage::$page(p) => p,
|
||||
_ => return,
|
||||
}
|
||||
}};
|
||||
(public page $page: expr, $page_name: ident) => {{
|
||||
let p = match $page {
|
||||
crate::pages::Page::Public(p) => p,
|
||||
@ -66,6 +86,22 @@ macro_rules! fetch_page {
|
||||
}
|
||||
}
|
||||
}};
|
||||
(admin page $page: expr, $page_name: ident, $ret: expr) => {{
|
||||
let p = match $page {
|
||||
crate::pages::Page::Admin(p) => p,
|
||||
_ => {
|
||||
*$page = $ret;
|
||||
return;
|
||||
}
|
||||
};
|
||||
match p {
|
||||
crate::pages::AdminPage::$page_name(p) => p,
|
||||
_ => {
|
||||
*$page = $ret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
@ -99,6 +135,10 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
}
|
||||
|
||||
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
#[cfg(debug_assertions)]
|
||||
if !matches!(msg, Msg::Session(SessionMsg::CheckSession)) {
|
||||
seed::log!("msg", msg);
|
||||
}
|
||||
match msg {
|
||||
#[cfg(debug_assertions)]
|
||||
Msg::NoOp => {
|
||||
@ -135,7 +175,10 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
let page = fetch_page!(public model, ShoppingCart);
|
||||
pages::public::shopping_cart::update(msg, page, &mut orders.proxy(Into::into))
|
||||
}
|
||||
Msg::Admin(_) => {}
|
||||
Msg::Admin(pages::admin::Msg::Landing(msg)) => {
|
||||
let page = fetch_page!(admin model, Landing);
|
||||
pages::admin::landing::update(msg, page, &mut orders.proxy(Into::into))
|
||||
}
|
||||
Msg::Session(msg) => {
|
||||
session::update(msg, model, orders);
|
||||
}
|
||||
@ -158,6 +201,7 @@ fn view(model: &Model) -> Node<Msg> {
|
||||
Page::Public(PublicPage::ShoppingCart(page)) => {
|
||||
pages::public::shopping_cart::view(model, page)
|
||||
}
|
||||
Page::Admin(AdminPage::Landing(page)) => pages::admin::landing::view(model, page),
|
||||
_ => empty![],
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,7 @@ pub enum Msg {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AdminPage {
|
||||
Landing,
|
||||
Landing(admin::landing::SignInPage),
|
||||
Dashboard,
|
||||
Products,
|
||||
Product,
|
||||
@ -70,7 +70,10 @@ impl Page {
|
||||
url,
|
||||
&mut orders.proxy(Into::into),
|
||||
))),
|
||||
["admin"] => Self::Admin(AdminPage::Landing),
|
||||
["admin"] => Self::Admin(AdminPage::Landing(admin::landing::init(
|
||||
url,
|
||||
&mut orders.proxy(Into::into),
|
||||
))),
|
||||
_ => Self::Public(PublicPage::Listing(public::listing::init(
|
||||
url,
|
||||
&mut orders.proxy(Into::into),
|
||||
@ -105,7 +108,10 @@ impl Page {
|
||||
let page = crate::fetch_page!(public page self, SignUp, Page::init(url, orders));
|
||||
public::sign_up::page_changed(url, page);
|
||||
}
|
||||
["admin"] => {}
|
||||
["admin"] => {
|
||||
let page = crate::fetch_page!(admin page self, Landing, Page::init(url, orders));
|
||||
admin::landing::page_changed(url, page);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
mod landing;
|
||||
pub mod landing;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
Landing(landing::Msg),
|
||||
Landing(landing::LogInMsg),
|
||||
}
|
||||
|
||||
impl From<landing::Msg> for Msg {
|
||||
fn from(msg: landing::Msg) -> Self {
|
||||
impl From<landing::LogInMsg> for Msg {
|
||||
fn from(msg: landing::LogInMsg) -> Self {
|
||||
Self::Landing(msg)
|
||||
}
|
||||
}
|
||||
@ -16,3 +16,9 @@ impl From<Msg> for crate::Msg {
|
||||
crate::Msg::Admin(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<landing::LogInMsg> for crate::Msg {
|
||||
fn from(msg: landing::LogInMsg) -> Self {
|
||||
Self::Admin(msg.into())
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,114 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Msg {}
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::pages::Urls;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogInMsg {
|
||||
LoginChanged(String),
|
||||
PasswordChanged(String),
|
||||
Submit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SignInPage {
|
||||
pub login: model::Login,
|
||||
pub password: model::Password,
|
||||
}
|
||||
|
||||
pub fn init(mut _url: Url, _orders: &mut impl Orders<LogInMsg>) -> SignInPage {
|
||||
SignInPage {
|
||||
login: model::Login::new(""),
|
||||
password: model::Password::new(""),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_changed(_url: Url, _model: &mut SignInPage) {}
|
||||
|
||||
pub fn update(_msg: LogInMsg, _model: &mut SignInPage, _orders: &mut impl Orders<crate::Msg>) {}
|
||||
|
||||
pub fn view(model: &crate::Model, page: &SignInPage) -> Node<crate::Msg> {
|
||||
let home = Urls::new(&model.url).home();
|
||||
let admin_logo = model
|
||||
.logo
|
||||
.as_deref()
|
||||
.map(|src| {
|
||||
a![
|
||||
attrs![At::Href => home],
|
||||
img![attrs![At::Src => src], C!["m-auto"]]
|
||||
]
|
||||
})
|
||||
.unwrap_or_else(|| a![attrs![At::Href => home], "Home"]);
|
||||
let content = div![
|
||||
C!["relative flex flex-col justify-center min-h-screen overflow-hidden"],
|
||||
div![
|
||||
C!["w-full p-6 m-auto bg-white rounded shadow-lg ring-2 ring-indigo-800/50 lg:max-w-md"],
|
||||
h2![
|
||||
C!["text-3xl font-semibold text-center text-indigo-700"],
|
||||
admin_logo
|
||||
],
|
||||
h1![C!["text-2xl font-semibold text-center text-indigo-700 m-3"], "Admin Panel"],
|
||||
admin_sign_in_form(model, page),
|
||||
p![
|
||||
C!["mt-8 text-xs font-light text-center text-indigo-700"],
|
||||
model.i18n.t("Don't have an account?"),
|
||||
a![
|
||||
C!["font-medium text-indigo-600 hover:underline"],
|
||||
attrs![At::Href => Urls::new(&model.url).sign_up()],
|
||||
" ",
|
||||
model.i18n.t("Sign up")
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
.map_msg(Into::into);
|
||||
|
||||
div![content]
|
||||
}
|
||||
|
||||
fn admin_sign_in_form(model: &crate::Model, _page: &SignInPage) -> Node<LogInMsg> {
|
||||
form![
|
||||
C!["mt-6"],
|
||||
ev("submit", |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
LogInMsg::Submit
|
||||
}),
|
||||
div![
|
||||
label![attrs!["for" => "login"], C!["block text-sm text-indigo-800"], model.i18n.t("Login")],
|
||||
input![
|
||||
attrs!["type" => "text", "id" => "login"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(LogInMsg::LoginChanged)
|
||||
})
|
||||
]
|
||||
],
|
||||
div![
|
||||
C!["mt-4"],
|
||||
div![
|
||||
label![attrs!["for" => "password"], C!["block text-sm text-indigo-800"], model.i18n.t("Password")],
|
||||
input![attrs!["type" => "password", "id" => "password"], C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"]],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(LogInMsg::PasswordChanged)
|
||||
})
|
||||
],
|
||||
a![
|
||||
C!["text-xs text-indigo-600 hover:underline"],
|
||||
attrs![At::Href => Urls::new(&model.url).forgot_password()],
|
||||
model.i18n.t("Forget Password?"),
|
||||
],
|
||||
div![
|
||||
C!["mt-6"],
|
||||
button![
|
||||
C!["w-full px-4 py-2 tracking-wide text-white transition-colors duration-200 transform bg-indigo-700 rounded-md hover:bg-indigo-600 focus:outline-none focus:bg-indigo-600"],
|
||||
model.i18n.t("Log in")
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
@ -41,10 +41,10 @@ pub fn update(msg: ProductMsg, model: &mut ProductPage, _orders: &mut impl Order
|
||||
model.products.update(products.0);
|
||||
}
|
||||
ProductMsg::ProductsFetched(NetRes::Error(e)) => {
|
||||
seed::error!(e);
|
||||
seed::error!("fetch product error", e);
|
||||
}
|
||||
ProductMsg::ProductsFetched(NetRes::Http(e)) => {
|
||||
seed::error!(e);
|
||||
seed::error!("fetch product http", e);
|
||||
}
|
||||
ProductMsg::SelectImage(selected) => {
|
||||
model.selected_image = selected;
|
||||
|
@ -1,8 +1,10 @@
|
||||
use rusty_money::Money;
|
||||
use model::Quantity;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::api::NetRes;
|
||||
use crate::pages::Urls;
|
||||
use crate::shopping_cart::CartMsg;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShoppingCartMsg {
|
||||
@ -37,21 +39,26 @@ pub fn update(
|
||||
model.products.update(products.0);
|
||||
}
|
||||
ShoppingCartMsg::ProductsFetched(NetRes::Error(e)) => {
|
||||
seed::error!(e);
|
||||
seed::error!("fetch product error", e);
|
||||
}
|
||||
ShoppingCartMsg::ProductsFetched(NetRes::Http(e)) => {
|
||||
seed::error!(e);
|
||||
seed::error!("fetch product http", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||
div![
|
||||
let content = div![
|
||||
C!["flex justify-center my-6"],
|
||||
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"],
|
||||
products(model, page)
|
||||
]
|
||||
];
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar::view(model, &page.products),
|
||||
super::layout::view(model, content, None)
|
||||
]
|
||||
}
|
||||
|
||||
@ -122,11 +129,17 @@ fn item_view(
|
||||
.map(|photo| photo.url.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let product_id = product.id;
|
||||
let quantity_unit = product.quantity_unit;
|
||||
let product_url = Urls::new(&model.url)
|
||||
.product()
|
||||
.add_path_part(product.id.to_string());
|
||||
|
||||
tr![
|
||||
td![
|
||||
C!["hidden pb-4 md:table-cell"],
|
||||
a![
|
||||
attrs![At::Href => "#"],
|
||||
attrs![At::Href => product_url.clone()],
|
||||
img![attrs![
|
||||
At::Src => img,
|
||||
At::Class => "w-20 rounded",
|
||||
@ -135,13 +148,22 @@ fn item_view(
|
||||
]
|
||||
],
|
||||
td![a![
|
||||
attrs![At::Href => "#"],
|
||||
attrs![At::Href => product_url.clone()],
|
||||
p![C!["mb-2 md:ml-4"], product.name.as_str()],
|
||||
form![
|
||||
attrs![ "action" => "", "method" => "POST"],
|
||||
ev(Ev::Submit, move |ev| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
crate::Msg::from(CartMsg::Remove(product_id))
|
||||
}),
|
||||
button![
|
||||
attrs!["type" => "submit", "class" => "text-gray-700 md:ml-4"],
|
||||
small![model.i18n.t("(Remove item)]")]
|
||||
attrs![At::Type => "submit", At::Class => "text-gray-700 md:ml-4 text-red-600"],
|
||||
small![model.i18n.t("(Remove item)")],
|
||||
ev(Ev::Click, move |ev| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
crate::Msg::from(CartMsg::Remove(product_id))
|
||||
})
|
||||
]
|
||||
]
|
||||
]],
|
||||
@ -151,11 +173,26 @@ fn item_view(
|
||||
C!["w-20 h-10"],
|
||||
div![
|
||||
C!["relative flex flex-row w-full h-8"],
|
||||
input![attrs![
|
||||
At::Type => "number",
|
||||
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"
|
||||
]],
|
||||
input![
|
||||
attrs![
|
||||
At::Type => "number",
|
||||
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"
|
||||
],
|
||||
ev(Ev::Change, move |ev| {
|
||||
ev.stop_propagation();
|
||||
let target = ev.target()?;
|
||||
let input = seed::to_input(&target);
|
||||
let value: u32 = input.value().parse().ok()?;
|
||||
let quantity = Quantity::from_u32(value);
|
||||
|
||||
Some(crate::Msg::from(CartMsg::ModifyItem {
|
||||
product_id,
|
||||
quantity_unit,
|
||||
quantity,
|
||||
}))
|
||||
})
|
||||
],
|
||||
]
|
||||
]
|
||||
],
|
||||
|
@ -38,10 +38,10 @@ pub fn init(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
|
||||
pub fn redirect_on_session(model: &Model, orders: &mut impl Orders<Msg>) {
|
||||
seed::log!(&model.page, model.shared.me.is_some());
|
||||
// seed::log!(&model.page, model.shared.me.is_some());
|
||||
match &model.page {
|
||||
Page::Admin(admin) => match admin {
|
||||
AdminPage::Landing => {}
|
||||
AdminPage::Landing(_) => {}
|
||||
AdminPage::Dashboard => {}
|
||||
AdminPage::Products => {}
|
||||
AdminPage::Product => {}
|
||||
@ -152,7 +152,7 @@ pub fn update(msg: SessionMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
||||
seed::error!("net", net);
|
||||
}
|
||||
FetchError::RequestError(e) => {
|
||||
seed::error!(e);
|
||||
seed::error!("request error", e);
|
||||
}
|
||||
FetchError::StatusError(status) => match status.code {
|
||||
401 | 403 => {
|
||||
|
@ -16,6 +16,7 @@ pub enum CartMsg {
|
||||
quantity_unit: QuantityUnit,
|
||||
product_id: ProductId,
|
||||
},
|
||||
Remove(ProductId),
|
||||
Hover,
|
||||
Leave,
|
||||
}
|
||||
@ -57,7 +58,7 @@ pub fn update(msg: CartMsg, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||
{
|
||||
let items: &mut Items = &mut model.cart.items;
|
||||
let entry: &mut Item = items.entry(product_id).or_insert_with(|| Item {
|
||||
quantity,
|
||||
quantity: Quantity::from_u32(0),
|
||||
quantity_unit,
|
||||
product_id,
|
||||
});
|
||||
@ -66,7 +67,29 @@ pub fn update(msg: CartMsg, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
store_local(&model.cart);
|
||||
}
|
||||
CartMsg::ModifyItem { .. } => {}
|
||||
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);
|
||||
}
|
||||
CartMsg::Remove(product_id) => {
|
||||
model.cart.items.remove(&product_id);
|
||||
store_local(&model.cart);
|
||||
}
|
||||
CartMsg::Hover => {
|
||||
model.cart.hover = true;
|
||||
}
|
||||
@ -80,7 +103,7 @@ fn load_local() -> ShoppingCart {
|
||||
match LocalStorage::get("ct") {
|
||||
Ok(cart) => cart,
|
||||
Err(e) => {
|
||||
seed::error!(e);
|
||||
seed::error!("Storage error", e);
|
||||
ShoppingCart::default()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user