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))]
#[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,

View File

@ -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,

View File

@ -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(),

View File

@ -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 {

View File

@ -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]
]
]
}
}

View File

@ -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 {
orders.send_msg(Msg::FetchProducts);
Model {
products: vec![],
errors: vec![],
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);
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),
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
)
]
}