Start paths
This commit is contained in:
parent
17c2997d3d
commit
3cc25ee126
@ -153,7 +153,7 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Photo {
|
||||
pub id: crate::PhotoId,
|
||||
pub file_name: crate::FileName,
|
||||
@ -162,7 +162,7 @@ pub struct Photo {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Product {
|
||||
pub id: crate::ProductId,
|
||||
pub name: crate::ProductName,
|
||||
|
@ -19,7 +19,7 @@ use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
pub use crate::encrypt::*;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug, Hash, thiserror::Error)]
|
||||
pub enum TransformError {
|
||||
#[error("Given value is below minimal value")]
|
||||
BelowMinimal,
|
||||
@ -34,7 +34,7 @@ pub type RecordId = i32;
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OrderStatus {
|
||||
#[display(fmt = "Potwierdzone")]
|
||||
@ -54,7 +54,7 @@ pub enum OrderStatus {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Role {
|
||||
#[display(fmt = "Adminitrator")]
|
||||
@ -80,7 +80,7 @@ impl Role {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QuantityUnit {
|
||||
#[cfg_attr(feature = "db", sqlx(rename = "g"))]
|
||||
@ -96,7 +96,7 @@ pub enum QuantityUnit {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PaymentMethod {
|
||||
PayU,
|
||||
@ -106,7 +106,7 @@ pub enum PaymentMethod {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShoppingCartState {
|
||||
Active,
|
||||
@ -116,7 +116,7 @@ pub enum ShoppingCartState {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Audience {
|
||||
Web,
|
||||
@ -161,14 +161,14 @@ impl Default for Audience {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Hash, Deref, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct Price(NonNegative);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Hash, Deref, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct Quantity(NonNegative);
|
||||
|
||||
@ -241,7 +241,7 @@ impl<'de> serde::Deserialize<'de> for Email {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Default, Debug, Copy, Clone, Deref, Display)]
|
||||
#[derive(Serialize, Default, Debug, Copy, Clone, Hash, Deref, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct NonNegative(i32);
|
||||
|
||||
@ -323,7 +323,7 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Display, From)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Day {
|
||||
Monday = 1 << 0,
|
||||
@ -360,7 +360,7 @@ impl TryFrom<i32> for Day {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Deref, Debug)]
|
||||
#[derive(Serialize, Deserialize, Hash, Debug, Deref)]
|
||||
#[serde(transparent)]
|
||||
pub struct Days(Vec<Day>);
|
||||
|
||||
@ -536,26 +536,26 @@ pub struct ProductId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductName(String);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductShortDesc(String);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductLongDesc(String);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductCategory(String);
|
||||
|
||||
@ -567,7 +567,7 @@ impl ProductCategory {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Product {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
@ -780,7 +780,7 @@ impl RefreshTokenString {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
pub struct LocalPath(String);
|
||||
|
||||
impl LocalPath {
|
||||
@ -792,7 +792,7 @@ impl LocalPath {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
pub struct UniqueName(String);
|
||||
|
||||
impl UniqueName {
|
||||
@ -804,7 +804,7 @@ impl UniqueName {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
pub struct FileName(String);
|
||||
|
||||
impl FileName {
|
||||
@ -816,18 +816,18 @@ impl FileName {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Deref, Display, From)]
|
||||
pub struct PhotoId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
pub struct ProductPhotoId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Photo {
|
||||
pub id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
@ -837,7 +837,7 @@ pub struct Photo {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct ProductLinkedPhoto {
|
||||
pub id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
|
@ -24,7 +24,9 @@ macro_rules! fetch_page {
|
||||
}
|
||||
|
||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
orders.stream(streams::interval(500, || Msg::CheckAccessToken));
|
||||
orders
|
||||
.stream(streams::interval(500, || Msg::CheckAccessToken))
|
||||
.subscribe(Msg::UrlChanged);
|
||||
|
||||
Model {
|
||||
token: LocalStorage::get("auth-token").ok(),
|
||||
|
@ -40,6 +40,13 @@ impl Page {
|
||||
url,
|
||||
&mut orders.proxy(|msg| Msg::Public(public::Msg::Listing(msg))),
|
||||
))),
|
||||
["products", rest @ ..] => {
|
||||
seed::log!(rest);
|
||||
Self::Public(PublicPage::Listing(public::listing::init(
|
||||
url,
|
||||
&mut orders.proxy(|msg| Msg::Public(public::Msg::Listing(msg))),
|
||||
)))
|
||||
}
|
||||
["admin"] => Self::Admin(AdminPage::Landing),
|
||||
_ => Self::Public(PublicPage::Listing(public::listing::init(
|
||||
url,
|
||||
@ -62,7 +69,7 @@ impl<'a> Urls<'a> {
|
||||
// Public
|
||||
|
||||
fn listing(self) -> Url {
|
||||
self.base_url().add_path_part("listing")
|
||||
self.base_url().add_path_part("products")
|
||||
}
|
||||
|
||||
fn product(self) -> Url {
|
||||
|
@ -4,3 +4,49 @@ pub mod listing;
|
||||
pub enum Msg {
|
||||
Listing(listing::Msg),
|
||||
}
|
||||
|
||||
pub mod layout {
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
pub fn view<Msg>(url: Url, content: Node<Msg>, categories: &[String]) -> Node<Msg> {
|
||||
div![
|
||||
C!["flex"],
|
||||
div![
|
||||
C!["flex flex-col w-64 h-screen px-4 py-8 overflow-y-auto border-r"],
|
||||
super::sidebar::view(url, categories)
|
||||
],
|
||||
div![C!["w-full h-full p-4 m-8 overflow-y-auto"], content]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub mod sidebar {
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::pages::Urls;
|
||||
|
||||
pub fn view<Msg>(url: Url, categories: &[String]) -> Node<Msg> {
|
||||
let categories = categories
|
||||
.iter()
|
||||
.map(|category| item(url.clone(), category.as_str()));
|
||||
|
||||
div![
|
||||
C!["flex flex-col justify-between mt-6"],
|
||||
aside![ul![categories]]
|
||||
]
|
||||
}
|
||||
|
||||
fn item<Msg>(url: Url, category: &str) -> Node<Msg> {
|
||||
let url = Urls::new(url).listing().add_path_part(category);
|
||||
li![
|
||||
C!["flex items-center px-4 py-2 text-gray-700 rounded-md"],
|
||||
a![
|
||||
C!["flex items-center px-4 py-2 text-gray-700 rounded-md"],
|
||||
attrs!["href" => url],
|
||||
span![C!["mx-4 font-medium"], category]
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,49 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use seed::app::Orders;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
pub products: Vec<model::api::Product>,
|
||||
url: Url,
|
||||
pub products: HashMap<model::ProductId, model::api::Product>,
|
||||
pub errors: Vec<String>,
|
||||
pub categories: Vec<String>,
|
||||
pub filters: HashSet<String>,
|
||||
pub visible_products: Vec<model::ProductId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
FetchProducts,
|
||||
ProductFetched(fetch::Result<model::api::Products>),
|
||||
Shared(crate::shared::Msg)
|
||||
Shared(crate::shared::Msg),
|
||||
}
|
||||
|
||||
pub fn init(_url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
let filters = match url.clone().remaining_path_parts().as_slice() {
|
||||
["products", filters @ ..] => {
|
||||
filters
|
||||
.into_iter()
|
||||
.fold(HashSet::with_capacity(filters.len()), |mut s, filter| {
|
||||
s.insert(String::from(*filter));
|
||||
s
|
||||
})
|
||||
}
|
||||
_ => HashSet::new(),
|
||||
};
|
||||
orders.send_msg(Msg::FetchProducts);
|
||||
Model {
|
||||
products: vec![],
|
||||
let model = Model {
|
||||
url: url.to_base_url(),
|
||||
products: Default::default(),
|
||||
errors: vec![],
|
||||
}
|
||||
categories: vec![],
|
||||
filters,
|
||||
visible_products: vec![],
|
||||
};
|
||||
seed::log!(&model);
|
||||
model
|
||||
}
|
||||
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
@ -31,7 +54,38 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
});
|
||||
}
|
||||
Msg::ProductFetched(Ok(products)) => {
|
||||
model.products = products.0;
|
||||
model.categories = products
|
||||
.0
|
||||
.iter()
|
||||
.fold(HashSet::new(), |mut set, p| {
|
||||
if let Some(category) = p.category.as_deref() {
|
||||
set.insert(String::from(category));
|
||||
}
|
||||
set
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
model.categories.sort();
|
||||
model.products = {
|
||||
let len = products.0.len();
|
||||
products
|
||||
.0
|
||||
.into_iter()
|
||||
.fold(HashMap::with_capacity(len), |mut m, p| {
|
||||
m.insert(p.id, p);
|
||||
m
|
||||
})
|
||||
};
|
||||
model.visible_products = model
|
||||
.products
|
||||
.iter()
|
||||
.filter_map(|(_, p)| {
|
||||
p.category
|
||||
.as_deref()
|
||||
.filter(|c| model.filters.contains(*c))
|
||||
.map(|_| p.id)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
Msg::ProductFetched(Err(_e)) => {
|
||||
model.errors.push("Failed to load products".into());
|
||||
@ -41,14 +95,22 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
|
||||
pub fn view(model: &crate::Model, page: &Model) -> Node<Msg> {
|
||||
let products = page.products.iter().map(product);
|
||||
let products = page
|
||||
.visible_products
|
||||
.iter()
|
||||
.filter_map(|id| page.products.get(id))
|
||||
.map(product);
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar(model),
|
||||
div![
|
||||
C!["grid grid-cols-1 gap-4 lg:grid-cols-6 sm:grid-cols-2"],
|
||||
products
|
||||
]
|
||||
super::layout::view(
|
||||
page.url.clone(),
|
||||
div![
|
||||
C!["grid grid-cols-1 gap-4 lg:grid-cols-6 sm:grid-cols-2"],
|
||||
products
|
||||
],
|
||||
&page.categories
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user