From 90b65bf6b22f5ebda8d9c58888c1f2b20b1838c0 Mon Sep 17 00:00:00 2001 From: Miroito Date: Tue, 7 Feb 2023 10:51:00 +0100 Subject: [PATCH] feat/Introduce config module --- Cargo.toml | 1 - client/index.html | 9 ---- client/src/api/routes/transaction.rs | 17 +++++-- client/src/components/base_async_select.rs | 15 ++++-- client/src/components/paginated_data_table.rs | 49 ++++++++++--------- client/src/env.rs | 15 ++++++ client/src/global_state.rs | 8 ++- client/src/lib.rs | 15 +++++- client/src/templates/index.rs | 35 ++++++++----- client/static/tailwind.css | 49 +++++-------------- makefile | 1 + 11 files changed, 121 insertions(+), 93 deletions(-) delete mode 100644 client/index.html create mode 100644 client/src/env.rs diff --git a/Cargo.toml b/Cargo.toml index e054d51..dc89f2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" server = { version = "0.1.0", path = "./server" } client = { version = "0.1.0", path = "./client" } - [workspace] members = ["server", "client"] diff --git a/client/index.html b/client/index.html deleted file mode 100644 index 00420c4..0000000 --- a/client/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/client/src/api/routes/transaction.rs b/client/src/api/routes/transaction.rs index b43ae20..d1b9dc5 100644 --- a/client/src/api/routes/transaction.rs +++ b/client/src/api/routes/transaction.rs @@ -1,19 +1,26 @@ -use crate::api::types::{paginated_response::PaginatedResponse, transaction::TransactionCompany}; +use crate::{ + api::types::{paginated_response::PaginatedResponse, transaction::TransactionCompany}, + env::Config, +}; pub async fn get_transactions( + company_slug: Option, page: i64, size: i64, ) -> Result, ()> { let res = reqwasm::http::Request::get(&format!( - "http://localhost:8000/v1/transaction?page={}&size={}", - page, size, + "{}transaction{}?page={}&size={}", + Config::new().api_url, + company_slug.unwrap_or("".to_string()), + page, + size, )) .send() .await - .unwrap() + .map_err(|_| ())? .json::>() .await - .unwrap(); + .map_err(|_| ())?; Ok(res) } diff --git a/client/src/components/base_async_select.rs b/client/src/components/base_async_select.rs index 53a1f2b..73ca286 100644 --- a/client/src/components/base_async_select.rs +++ b/client/src/components/base_async_select.rs @@ -10,11 +10,17 @@ pub struct AsyncSelectRx where T: 'static + PartialEq + Clone + IntoAsyncSelectListItem, { + pub remote_list: ReadSignal, pub selected_item: Signal>, } #[component(BaseAsyncSelect)] -pub fn create_component(AsyncSelectRx { selected_item }: AsyncSelectRx) -> View +pub fn create_component( + AsyncSelectRx { + remote_list, + selected_item, + }: AsyncSelectRx, +) -> View where T: 'static + PartialEq + Clone + IntoAsyncSelectListItem, for<'de> T: Deserialize<'de>, @@ -31,7 +37,7 @@ where let item_list: Signal> = Signal::new(vec![]); let selected = Signal::new(false); create_effect( - cloned!((input, visible, item_list, selected, selected_item) => move || { + cloned!((input, visible, item_list, selected, selected_item, remote_list) => move || { // Early return if: // - The input is empty, there is nothing to search for nor to show // - We just selected an item @@ -42,12 +48,11 @@ where } selected_item.set(None); - let url = "http://localhost:8000/v1/company/"; if G::IS_BROWSER { perseus::spawn_local( - cloned!((input, visible, item_list) => async move { + cloned!((input, visible, item_list, remote_list) => async move { let res = reqwasm::http::Request::get( - &format!( "{}{}?limit={}", url, input.get(), 5) + &format!( "{}/{}?limit={}", remote_list.get(), input.get(), 5) ) .send() .await diff --git a/client/src/components/paginated_data_table.rs b/client/src/components/paginated_data_table.rs index d5193c9..82f24e5 100644 --- a/client/src/components/paginated_data_table.rs +++ b/client/src/components/paginated_data_table.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, rc::Rc}; +use std::rc::Rc; use serde::Deserialize; use sycamore::prelude::*; @@ -11,21 +11,36 @@ use crate::{ }, }; -#[perseus::make_rx(PaginatedTableStateRx)] -pub struct PaginatedTableState +#[derive(Clone)] +pub struct PaginatedTableStateRx where M: 'static, + C: Fn(Option, i64, i64) -> F, + F: std::future::Future, ()>>, { - pub req: String, - pub ph_data: PhantomData, + pub route: C, + pub filter: Option, } -#[component(PaginatedTable)] -pub fn component(PaginatedTableStateRx { req, ph_data }: PaginatedTableStateRx) -> View +impl PaginatedTableStateRx where M: 'static, - PaginatedResponse: IntoTableData + Clone, + C: Fn(Option, i64, i64) -> F, + F: std::future::Future, ()>>, +{ + async fn get_data(&self, page: i64, size: i64) -> Result, ()> { + (self.route)(self.filter.clone(), page, size).await + } +} + +#[component(PaginatedTable)] +pub fn component(state: PaginatedTableStateRx) -> View +where + M: 'static + Clone, + PaginatedResponse: IntoTableData, for<'de> M: Deserialize<'de>, + C: Fn(Option, i64, i64) -> F + 'static, + F: std::future::Future, ()>> + 'static, { let paginated_data: Signal>> = Signal::new(None); let table_prop: TableContentRx = TableContentRx { @@ -51,26 +66,16 @@ where let page_size_string = Signal::new("20".to_string()); let page_size_string2 = page_size_string.clone(); + let state_rc = Rc::new(state); create_effect( - cloned!((page_size_string, paginated_data, page, req, n_page, n_rows) => move || { + cloned!((page_size_string, paginated_data, page, n_page, n_rows, state_rc) => move || { let page = *page.get(); let page_size_s = page_size_string.get(); let page_size = page_size_s.parse().unwrap_or(20); - let url = (*req.get()).clone(); if G::IS_BROWSER { perseus::spawn_local( - cloned!((table_prop2, page, paginated_data, n_page, n_rows) => async move { - let res = reqwasm::http::Request::get(&format!( - "{}?page={}&size={}", - url, - page, page_size, - )) - .send() - .await - .unwrap() - .json::>() - .await - .unwrap(); + cloned!((table_prop2, page, paginated_data, n_page, n_rows, state_rc) => async move { + let res = state_rc.get_data(page, page_size).await.unwrap(); paginated_data.set(Some(res.clone())); n_rows.set(res.count); let table_content = res.into_table_data(); diff --git a/client/src/env.rs b/client/src/env.rs new file mode 100644 index 0000000..e10261d --- /dev/null +++ b/client/src/env.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use std::env; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Config { + pub api_url: String, +} + +impl Config { + pub fn new() -> Self { + let api_url = env!("API_URL").to_string(); + Config { api_url } + } +} diff --git a/client/src/global_state.rs b/client/src/global_state.rs index 54c8ddb..483273b 100644 --- a/client/src/global_state.rs +++ b/client/src/global_state.rs @@ -1,3 +1,4 @@ +use crate::env::Config; use perseus::{state::GlobalStateCreator, RenderFnResult}; pub fn get_global_state_creator() -> GlobalStateCreator { @@ -7,9 +8,14 @@ pub fn get_global_state_creator() -> GlobalStateCreator { #[perseus::make_rx(AppStateRx)] pub struct AppState { pub dark_mode: bool, + pub config: Config, } #[perseus::autoserde(global_build_state)] pub async fn get_build_state() -> RenderFnResult { - Ok(AppState { dark_mode: true }) + let config = Config::new(); + Ok(AppState { + config, + dark_mode: true, + }) } diff --git a/client/src/lib.rs b/client/src/lib.rs index 6bad56c..859dbf9 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,7 +1,9 @@ -use perseus::{Html, PerseusApp}; +use perseus::{Html, PerseusApp, PerseusRoot}; +use sycamore::view; mod api; mod components; +mod env; pub mod error_pages; pub mod global_state; pub mod templates; @@ -12,4 +14,15 @@ pub fn main() -> PerseusApp { .template(crate::templates::index::get_template) .global_state_creator(crate::global_state::get_global_state_creator()) .error_pages(crate::error_pages::get_error_pages) + .index_view(|| { + view! { + head() { + title { "Fast Insiders" } + link (rel="stylesheet", href = "/.perseus/static/tailwind.css") {} + } + body { + PerseusRoot() + } + } + }) } diff --git a/client/src/templates/index.rs b/client/src/templates/index.rs index 543ae2b..cf44c80 100644 --- a/client/src/templates/index.rs +++ b/client/src/templates/index.rs @@ -1,25 +1,30 @@ -use std::marker::PhantomData; - use perseus::{navigate, Html, RenderFnResult, RenderFnResultWithCause, SsrNode, Template}; use sycamore::prelude::*; use crate::{ - api::types::{company::Company, transaction::TransactionCompany}, + api::{ + routes::transaction::get_transactions, + types::{company::Company, transaction::TransactionCompany}, + }, components::{ base_async_select::{AsyncSelectRx, BaseAsyncSelect}, base_button::{BaseButton, BaseButtonStateRx}, paginated_data_table::{PaginatedTable, PaginatedTableStateRx}, }, + env::Config, global_state::AppStateRx, }; #[perseus::make_rx(IndexPageStateRx)] pub struct IndexPageState { - pub req: String, + pub company_slug: String, } #[perseus::template_rx] -pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppStateRx) -> View { +pub fn index_page( + IndexPageStateRx { company_slug }: IndexPageStateRx, + global_state: AppStateRx, +) -> View { let dark_mode = global_state.dark_mode; let dark_mode_2 = dark_mode.clone(); let dark_mode_3 = dark_mode.clone(); @@ -40,12 +45,18 @@ pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppS let toggle_dark_mode = cloned!(() => move |_| dark_mode_2.set(!*dark_mode.get())); - let paginated_table_state: PaginatedTableStateRx = PaginatedTableStateRx { - req, - ph_data: Signal::new(PhantomData), - }; + let paginated_table_state: PaginatedTableStateRx = + PaginatedTableStateRx { + route: get_transactions, + filter: if (*company_slug.get()).is_empty() { + None + } else { + Some((*company_slug.get()).clone()) + }, + }; let async_select_prop: AsyncSelectRx = AsyncSelectRx { + remote_list: Signal::new(format!("{}company/", global_state.config.get().api_url)).handle(), selected_item: Signal::new(None), }; let async_select_prop2 = async_select_prop.clone(); @@ -67,7 +78,6 @@ pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppS view! { main (class=if *dark_mode_3.get() { "dark" } else { "" }) { - link (rel="stylesheet", href = "/.perseus/static/tailwind.css") {} div (class="bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-100 font-sans") { header (class="shadow-md h-12 p-2 align-middle w-full bg-gray-100 dark:bg-slate-500/30 backdrop-blur-lg") { div (class="flex flex-row justify-between") { @@ -101,7 +111,7 @@ pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppS div (class="w-80") { p () {"Search for a company:"} BaseAsyncSelect(async_select_prop) - BaseButton(search_button) + BaseButton(search_button) } } PaginatedTable(paginated_table_state) @@ -134,8 +144,7 @@ pub async fn get_build_state( _locale: String, ) -> RenderFnResultWithCause { let company_slug: String = path.clone().drain("index".len()..).collect(); - let req = format!("http://localhost:8000/v1/transaction{}", company_slug); - Ok(IndexPageState { req }) + Ok(IndexPageState { company_slug }) } pub async fn get_build_paths() -> RenderFnResult> { diff --git a/client/static/tailwind.css b/client/static/tailwind.css index 6b8bbf1..c1ee8d9 100644 --- a/client/static/tailwind.css +++ b/client/static/tailwind.css @@ -588,10 +588,6 @@ video { display: table; } -.hidden { - display: none; -} - .h-10 { height: 2.5rem; } @@ -608,14 +604,6 @@ video { height: 0px; } -.h-auto { - height: auto; -} - -.h-\[320\] { - height: 320; -} - .w-full { width: 100%; } @@ -708,19 +696,14 @@ video { border-color: rgb(226 232 240 / var(--tw-border-opacity)); } -.bg-slate-200 { - --tw-bg-opacity: 1; - background-color: rgb(226 232 240 / var(--tw-bg-opacity)); -} - .bg-slate-300 { --tw-bg-opacity: 1; background-color: rgb(203 213 225 / var(--tw-bg-opacity)); } -.bg-slate-100 { +.bg-slate-200 { --tw-bg-opacity: 1; - background-color: rgb(241 245 249 / var(--tw-bg-opacity)); + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); } .bg-gray-100 { @@ -733,9 +716,9 @@ video { background-color: rgb(251 207 232 / var(--tw-bg-opacity)); } -.bg-slate-700 { +.bg-slate-100 { --tw-bg-opacity: 1; - background-color: rgb(51 65 85 / var(--tw-bg-opacity)); + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); } .p-2 { @@ -805,6 +788,10 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + .backdrop-blur-lg { --tw-backdrop-blur: blur(16px); -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); @@ -825,11 +812,6 @@ video { cursor: pointer; } -.hover\:bg-slate-300:hover { - --tw-bg-opacity: 1; - background-color: rgb(203 213 225 / var(--tw-bg-opacity)); -} - .hover\:bg-slate-400:hover { --tw-bg-opacity: 1; background-color: rgb(148 163 184 / var(--tw-bg-opacity)); @@ -857,11 +839,6 @@ video { border-color: rgb(30 41 59 / var(--tw-border-opacity)); } -.dark .dark\:bg-slate-700 { - --tw-bg-opacity: 1; - background-color: rgb(51 65 85 / var(--tw-bg-opacity)); -} - .dark .dark\:bg-slate-800 { --tw-bg-opacity: 1; background-color: rgb(30 41 59 / var(--tw-bg-opacity)); @@ -876,6 +853,11 @@ video { background-color: rgb(219 39 119 / var(--tw-bg-opacity)); } +.dark .dark\:bg-slate-700 { + --tw-bg-opacity: 1; + background-color: rgb(51 65 85 / var(--tw-bg-opacity)); +} + .dark .dark\:bg-slate-600 { --tw-bg-opacity: 1; background-color: rgb(71 85 105 / var(--tw-bg-opacity)); @@ -891,11 +873,6 @@ video { color: rgb(165 180 252 / var(--tw-text-opacity)); } -.dark .dark\:hover\:bg-slate-800:hover { - --tw-bg-opacity: 1; - background-color: rgb(30 41 59 / var(--tw-bg-opacity)); -} - .dark .dark\:hover\:bg-slate-900:hover { --tw-bg-opacity: 1; background-color: rgb(15 23 42 / var(--tw-bg-opacity)); diff --git a/makefile b/makefile index 3cb6228..a5ce10d 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,5 @@ include .env +export API_URL # So the client can use it # Migrations should be written first and the model files can be created using this command db_entities: -- 2.36.3