Start paths

This commit is contained in:
eraden 2022-05-10 22:32:22 +02:00
parent 17c2997d3d
commit 3cc25ee126
6 changed files with 157 additions and 40 deletions

View File

@ -153,7 +153,7 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Hash)]
pub struct Photo { pub struct Photo {
pub id: crate::PhotoId, pub id: crate::PhotoId,
pub file_name: crate::FileName, pub file_name: crate::FileName,
@ -162,7 +162,7 @@ pub struct Photo {
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Hash)]
pub struct Product { pub struct Product {
pub id: crate::ProductId, pub id: crate::ProductId,
pub name: crate::ProductName, pub name: crate::ProductName,

View File

@ -19,7 +19,7 @@ use serde::{Deserialize, Deserializer, Serialize};
pub use crate::encrypt::*; pub use crate::encrypt::*;
#[derive(Debug, thiserror::Error)] #[derive(Debug, Hash, thiserror::Error)]
pub enum TransformError { pub enum TransformError {
#[error("Given value is below minimal value")] #[error("Given value is below minimal value")]
BelowMinimal, BelowMinimal,
@ -34,7 +34,7 @@ pub type RecordId = i32;
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))] #[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")] #[serde(rename_all = "snake_case")]
pub enum OrderStatus { pub enum OrderStatus {
#[display(fmt = "Potwierdzone")] #[display(fmt = "Potwierdzone")]
@ -54,7 +54,7 @@ pub enum OrderStatus {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))] #[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")] #[serde(rename_all = "snake_case")]
pub enum Role { pub enum Role {
#[display(fmt = "Adminitrator")] #[display(fmt = "Adminitrator")]
@ -80,7 +80,7 @@ impl Role {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[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")] #[serde(rename_all = "snake_case")]
pub enum QuantityUnit { pub enum QuantityUnit {
#[cfg_attr(feature = "db", sqlx(rename = "g"))] #[cfg_attr(feature = "db", sqlx(rename = "g"))]
@ -96,7 +96,7 @@ pub enum QuantityUnit {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))] #[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")] #[serde(rename_all = "snake_case")]
pub enum PaymentMethod { pub enum PaymentMethod {
PayU, PayU,
@ -106,7 +106,7 @@ pub enum PaymentMethod {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))] #[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")] #[serde(rename_all = "snake_case")]
pub enum ShoppingCartState { pub enum ShoppingCartState {
Active, Active,
@ -116,7 +116,7 @@ pub enum ShoppingCartState {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))] #[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")] #[serde(rename_all = "snake_case")]
pub enum Audience { pub enum Audience {
Web, Web,
@ -161,14 +161,14 @@ impl Default for Audience {
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct Price(NonNegative); pub struct Price(NonNegative);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct Quantity(NonNegative); 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", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct NonNegative(i32); pub struct NonNegative(i32);
@ -323,7 +323,7 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[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")] #[serde(rename_all = "lowercase")]
pub enum Day { pub enum Day {
Monday = 1 << 0, Monday = 1 << 0,
@ -360,7 +360,7 @@ impl TryFrom<i32> for Day {
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Deref, Debug)] #[derive(Serialize, Deserialize, Hash, Debug, Deref)]
#[serde(transparent)] #[serde(transparent)]
pub struct Days(Vec<Day>); pub struct Days(Vec<Day>);
@ -536,26 +536,26 @@ pub struct ProductId(RecordId);
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct ProductName(String); pub struct ProductName(String);
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct ProductShortDesc(String); pub struct ProductShortDesc(String);
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct ProductLongDesc(String); pub struct ProductLongDesc(String);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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)] #[serde(transparent)]
pub struct ProductCategory(String); pub struct ProductCategory(String);
@ -567,7 +567,7 @@ impl ProductCategory {
#[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, Debug)] #[derive(Serialize, Deserialize, Debug, Hash)]
pub struct Product { pub struct Product {
pub id: ProductId, pub id: ProductId,
pub name: ProductName, pub name: ProductName,
@ -780,7 +780,7 @@ impl RefreshTokenString {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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); pub struct LocalPath(String);
impl LocalPath { impl LocalPath {
@ -792,7 +792,7 @@ impl LocalPath {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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); pub struct UniqueName(String);
impl UniqueName { impl UniqueName {
@ -804,7 +804,7 @@ impl UniqueName {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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); pub struct FileName(String);
impl FileName { impl FileName {
@ -816,18 +816,18 @@ impl FileName {
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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); pub struct PhotoId(RecordId);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[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); pub struct ProductPhotoId(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, Debug)] #[derive(Serialize, Deserialize, Debug, Hash)]
pub struct Photo { pub struct Photo {
pub id: PhotoId, pub id: PhotoId,
pub local_path: LocalPath, pub local_path: LocalPath,
@ -837,7 +837,7 @@ pub struct Photo {
#[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, Debug)] #[derive(Serialize, Deserialize, Debug, Hash)]
pub struct ProductLinkedPhoto { pub struct ProductLinkedPhoto {
pub id: PhotoId, pub id: PhotoId,
pub local_path: LocalPath, pub local_path: LocalPath,

View File

@ -24,7 +24,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.stream(streams::interval(500, || Msg::CheckAccessToken)); orders
.stream(streams::interval(500, || Msg::CheckAccessToken))
.subscribe(Msg::UrlChanged);
Model { Model {
token: LocalStorage::get("auth-token").ok(), token: LocalStorage::get("auth-token").ok(),

View File

@ -40,6 +40,13 @@ impl Page {
url, url,
&mut orders.proxy(|msg| Msg::Public(public::Msg::Listing(msg))), &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), ["admin"] => Self::Admin(AdminPage::Landing),
_ => Self::Public(PublicPage::Listing(public::listing::init( _ => Self::Public(PublicPage::Listing(public::listing::init(
url, url,
@ -62,7 +69,7 @@ impl<'a> Urls<'a> {
// Public // Public
fn listing(self) -> Url { fn listing(self) -> Url {
self.base_url().add_path_part("listing") self.base_url().add_path_part("products")
} }
fn product(self) -> Url { fn product(self) -> Url {

View File

@ -4,3 +4,49 @@ pub mod listing;
pub enum Msg { pub enum Msg {
Listing(listing::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]
]
]
}
}

View File

@ -1,26 +1,49 @@
use std::collections::{HashMap, HashSet};
use seed::app::Orders; use seed::app::Orders;
use seed::prelude::*; use seed::prelude::*;
use seed::*; use seed::*;
#[derive(Debug)] #[derive(Debug)]
pub struct Model { pub struct Model {
pub products: Vec<model::api::Product>, url: Url,
pub products: HashMap<model::ProductId, model::api::Product>,
pub errors: Vec<String>, pub errors: Vec<String>,
pub categories: Vec<String>,
pub filters: HashSet<String>,
pub visible_products: Vec<model::ProductId>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Msg { pub enum Msg {
FetchProducts, FetchProducts,
ProductFetched(fetch::Result<model::api::Products>), 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 {
orders.send_msg(Msg::FetchProducts); let filters = match url.clone().remaining_path_parts().as_slice() {
Model { ["products", filters @ ..] => {
products: vec![], filters
errors: vec![], .into_iter()
.fold(HashSet::with_capacity(filters.len()), |mut s, filter| {
s.insert(String::from(*filter));
s
})
} }
_ => HashSet::new(),
};
orders.send_msg(Msg::FetchProducts);
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>) { 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)) => { 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)) => { Msg::ProductFetched(Err(_e)) => {
model.errors.push("Failed to load products".into()); 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> { 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![ div![
crate::shared::view::public_navbar(model), crate::shared::view::public_navbar(model),
super::layout::view(
page.url.clone(),
div![ div![
C!["grid grid-cols-1 gap-4 lg:grid-cols-6 sm:grid-cols-2"], C!["grid grid-cols-1 gap-4 lg:grid-cols-6 sm:grid-cols-2"],
products products
] ],
&page.categories
)
] ]
} }