feat/Introduce config module #7

Merged
alban merged 1 commits from config into master 3 years ago

@ -9,7 +9,6 @@ edition = "2021"
server = { version = "0.1.0", path = "./server" } server = { version = "0.1.0", path = "./server" }
client = { version = "0.1.0", path = "./client" } client = { version = "0.1.0", path = "./client" }
[workspace] [workspace]
members = ["server", "client"] members = ["server", "client"]

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body></body>
</html>

@ -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( pub async fn get_transactions(
company_slug: Option<String>,
page: i64, page: i64,
size: i64, size: i64,
) -> Result<PaginatedResponse<TransactionCompany>, ()> { ) -> Result<PaginatedResponse<TransactionCompany>, ()> {
let res = reqwasm::http::Request::get(&format!( let res = reqwasm::http::Request::get(&format!(
"http://localhost:8000/v1/transaction?page={}&size={}", "{}transaction{}?page={}&size={}",
page, size, Config::new().api_url,
company_slug.unwrap_or("".to_string()),
page,
size,
)) ))
.send() .send()
.await .await
.unwrap() .map_err(|_| ())?
.json::<PaginatedResponse<TransactionCompany>>() .json::<PaginatedResponse<TransactionCompany>>()
.await .await
.unwrap(); .map_err(|_| ())?;
Ok(res) Ok(res)
} }

@ -10,11 +10,17 @@ pub struct AsyncSelectRx<T>
where where
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem, T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
{ {
pub remote_list: ReadSignal<String>,
pub selected_item: Signal<Option<T>>, pub selected_item: Signal<Option<T>>,
} }
#[component(BaseAsyncSelect<G>)] #[component(BaseAsyncSelect<G>)]
pub fn create_component<T>(AsyncSelectRx { selected_item }: AsyncSelectRx<T>) -> View<G> pub fn create_component<T>(
AsyncSelectRx {
remote_list,
selected_item,
}: AsyncSelectRx<T>,
) -> View<G>
where where
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem, T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
for<'de> T: Deserialize<'de>, for<'de> T: Deserialize<'de>,
@ -31,7 +37,7 @@ where
let item_list: Signal<Vec<T>> = Signal::new(vec![]); let item_list: Signal<Vec<T>> = Signal::new(vec![]);
let selected = Signal::new(false); let selected = Signal::new(false);
create_effect( 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: // Early return if:
// - The input is empty, there is nothing to search for nor to show // - The input is empty, there is nothing to search for nor to show
// - We just selected an item // - We just selected an item
@ -42,12 +48,11 @@ where
} }
selected_item.set(None); selected_item.set(None);
let url = "http://localhost:8000/v1/company/";
if G::IS_BROWSER { if G::IS_BROWSER {
perseus::spawn_local( 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( let res = reqwasm::http::Request::get(
&format!( "{}{}?limit={}", url, input.get(), 5) &format!( "{}/{}?limit={}", remote_list.get(), input.get(), 5)
) )
.send() .send()
.await .await

@ -1,4 +1,4 @@
use std::{marker::PhantomData, rc::Rc}; use std::rc::Rc;
use serde::Deserialize; use serde::Deserialize;
use sycamore::prelude::*; use sycamore::prelude::*;
@ -11,21 +11,36 @@ use crate::{
}, },
}; };
#[perseus::make_rx(PaginatedTableStateRx)] #[derive(Clone)]
pub struct PaginatedTableState<M> pub struct PaginatedTableStateRx<M, F, C>
where where
M: 'static, M: 'static,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>>,
{ {
pub req: String, pub route: C,
pub ph_data: PhantomData<M>, pub filter: Option<String>,
} }
#[component(PaginatedTable<G>)] impl<M, F, C> PaginatedTableStateRx<M, F, C>
pub fn component<M>(PaginatedTableStateRx { req, ph_data }: PaginatedTableStateRx<M>) -> View<G>
where where
M: 'static, M: 'static,
PaginatedResponse<M>: IntoTableData<G> + Clone, C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>>,
{
async fn get_data(&self, page: i64, size: i64) -> Result<PaginatedResponse<M>, ()> {
(self.route)(self.filter.clone(), page, size).await
}
}
#[component(PaginatedTable<G>)]
pub fn component<M, F, C>(state: PaginatedTableStateRx<M, F, C>) -> View<G>
where
M: 'static + Clone,
PaginatedResponse<M>: IntoTableData<G>,
for<'de> M: Deserialize<'de>, for<'de> M: Deserialize<'de>,
C: Fn(Option<String>, i64, i64) -> F + 'static,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>> + 'static,
{ {
let paginated_data: Signal<Option<PaginatedResponse<M>>> = Signal::new(None); let paginated_data: Signal<Option<PaginatedResponse<M>>> = Signal::new(None);
let table_prop: TableContentRx<G> = TableContentRx { let table_prop: TableContentRx<G> = TableContentRx {
@ -51,26 +66,16 @@ where
let page_size_string = Signal::new("20".to_string()); let page_size_string = Signal::new("20".to_string());
let page_size_string2 = page_size_string.clone(); let page_size_string2 = page_size_string.clone();
let state_rc = Rc::new(state);
create_effect( 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 = *page.get();
let page_size_s = page_size_string.get(); let page_size_s = page_size_string.get();
let page_size = page_size_s.parse().unwrap_or(20); let page_size = page_size_s.parse().unwrap_or(20);
let url = (*req.get()).clone();
if G::IS_BROWSER { if G::IS_BROWSER {
perseus::spawn_local( perseus::spawn_local(
cloned!((table_prop2, page, paginated_data, n_page, n_rows) => async move { cloned!((table_prop2, page, paginated_data, n_page, n_rows, state_rc) => async move {
let res = reqwasm::http::Request::get(&format!( let res = state_rc.get_data(page, page_size).await.unwrap();
"{}?page={}&size={}",
url,
page, page_size,
))
.send()
.await
.unwrap()
.json::<PaginatedResponse<M>>()
.await
.unwrap();
paginated_data.set(Some(res.clone())); paginated_data.set(Some(res.clone()));
n_rows.set(res.count); n_rows.set(res.count);
let table_content = res.into_table_data(); let table_content = res.into_table_data();

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

@ -1,3 +1,4 @@
use crate::env::Config;
use perseus::{state::GlobalStateCreator, RenderFnResult}; use perseus::{state::GlobalStateCreator, RenderFnResult};
pub fn get_global_state_creator() -> GlobalStateCreator { pub fn get_global_state_creator() -> GlobalStateCreator {
@ -7,9 +8,14 @@ pub fn get_global_state_creator() -> GlobalStateCreator {
#[perseus::make_rx(AppStateRx)] #[perseus::make_rx(AppStateRx)]
pub struct AppState { pub struct AppState {
pub dark_mode: bool, pub dark_mode: bool,
pub config: Config,
} }
#[perseus::autoserde(global_build_state)] #[perseus::autoserde(global_build_state)]
pub async fn get_build_state() -> RenderFnResult<AppState> { pub async fn get_build_state() -> RenderFnResult<AppState> {
Ok(AppState { dark_mode: true }) let config = Config::new();
Ok(AppState {
config,
dark_mode: true,
})
} }

@ -1,7 +1,9 @@
use perseus::{Html, PerseusApp}; use perseus::{Html, PerseusApp, PerseusRoot};
use sycamore::view;
mod api; mod api;
mod components; mod components;
mod env;
pub mod error_pages; pub mod error_pages;
pub mod global_state; pub mod global_state;
pub mod templates; pub mod templates;
@ -12,4 +14,15 @@ pub fn main<G: Html>() -> PerseusApp<G> {
.template(crate::templates::index::get_template) .template(crate::templates::index::get_template)
.global_state_creator(crate::global_state::get_global_state_creator()) .global_state_creator(crate::global_state::get_global_state_creator())
.error_pages(crate::error_pages::get_error_pages) .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()
}
}
})
} }

@ -1,25 +1,30 @@
use std::marker::PhantomData;
use perseus::{navigate, Html, RenderFnResult, RenderFnResultWithCause, SsrNode, Template}; use perseus::{navigate, Html, RenderFnResult, RenderFnResultWithCause, SsrNode, Template};
use sycamore::prelude::*; use sycamore::prelude::*;
use crate::{ use crate::{
api::types::{company::Company, transaction::TransactionCompany}, api::{
routes::transaction::get_transactions,
types::{company::Company, transaction::TransactionCompany},
},
components::{ components::{
base_async_select::{AsyncSelectRx, BaseAsyncSelect}, base_async_select::{AsyncSelectRx, BaseAsyncSelect},
base_button::{BaseButton, BaseButtonStateRx}, base_button::{BaseButton, BaseButtonStateRx},
paginated_data_table::{PaginatedTable, PaginatedTableStateRx}, paginated_data_table::{PaginatedTable, PaginatedTableStateRx},
}, },
env::Config,
global_state::AppStateRx, global_state::AppStateRx,
}; };
#[perseus::make_rx(IndexPageStateRx)] #[perseus::make_rx(IndexPageStateRx)]
pub struct IndexPageState { pub struct IndexPageState {
pub req: String, pub company_slug: String,
} }
#[perseus::template_rx] #[perseus::template_rx]
pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppStateRx) -> View<G> { pub fn index_page(
IndexPageStateRx { company_slug }: IndexPageStateRx,
global_state: AppStateRx,
) -> View<G> {
let dark_mode = global_state.dark_mode; let dark_mode = global_state.dark_mode;
let dark_mode_2 = dark_mode.clone(); let dark_mode_2 = dark_mode.clone();
let dark_mode_3 = 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 toggle_dark_mode = cloned!(() => move |_| dark_mode_2.set(!*dark_mode.get()));
let paginated_table_state: PaginatedTableStateRx<TransactionCompany> = PaginatedTableStateRx { let paginated_table_state: PaginatedTableStateRx<TransactionCompany, _, _> =
req, PaginatedTableStateRx {
ph_data: Signal::new(PhantomData), route: get_transactions,
filter: if (*company_slug.get()).is_empty() {
None
} else {
Some((*company_slug.get()).clone())
},
}; };
let async_select_prop: AsyncSelectRx<Company> = AsyncSelectRx { let async_select_prop: AsyncSelectRx<Company> = AsyncSelectRx {
remote_list: Signal::new(format!("{}company/", global_state.config.get().api_url)).handle(),
selected_item: Signal::new(None), selected_item: Signal::new(None),
}; };
let async_select_prop2 = async_select_prop.clone(); let async_select_prop2 = async_select_prop.clone();
@ -67,7 +78,6 @@ pub fn index_page(IndexPageStateRx { req }: IndexPageStateRx, global_state: AppS
view! { view! {
main (class=if *dark_mode_3.get() { "dark" } else { "" }) { 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") { 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") { 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") { div (class="flex flex-row justify-between") {
@ -134,8 +144,7 @@ pub async fn get_build_state(
_locale: String, _locale: String,
) -> RenderFnResultWithCause<IndexPageState> { ) -> RenderFnResultWithCause<IndexPageState> {
let company_slug: String = path.clone().drain("index".len()..).collect(); let company_slug: String = path.clone().drain("index".len()..).collect();
let req = format!("http://localhost:8000/v1/transaction{}", company_slug); Ok(IndexPageState { company_slug })
Ok(IndexPageState { req })
} }
pub async fn get_build_paths() -> RenderFnResult<Vec<String>> { pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {

@ -588,10 +588,6 @@ video {
display: table; display: table;
} }
.hidden {
display: none;
}
.h-10 { .h-10 {
height: 2.5rem; height: 2.5rem;
} }
@ -608,14 +604,6 @@ video {
height: 0px; height: 0px;
} }
.h-auto {
height: auto;
}
.h-\[320\] {
height: 320;
}
.w-full { .w-full {
width: 100%; width: 100%;
} }
@ -708,19 +696,14 @@ video {
border-color: rgb(226 232 240 / var(--tw-border-opacity)); 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 { .bg-slate-300 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(203 213 225 / var(--tw-bg-opacity)); background-color: rgb(203 213 225 / var(--tw-bg-opacity));
} }
.bg-slate-100 { .bg-slate-200 {
--tw-bg-opacity: 1; --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 { .bg-gray-100 {
@ -733,9 +716,9 @@ video {
background-color: rgb(251 207 232 / var(--tw-bg-opacity)); background-color: rgb(251 207 232 / var(--tw-bg-opacity));
} }
.bg-slate-700 { .bg-slate-100 {
--tw-bg-opacity: 1; --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 { .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); 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 { .backdrop-blur-lg {
--tw-backdrop-blur: blur(16px); --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); -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; 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 { .hover\:bg-slate-400:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(148 163 184 / var(--tw-bg-opacity)); 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)); 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 { .dark .dark\:bg-slate-800 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity)); 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)); 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 { .dark .dark\:bg-slate-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity)); background-color: rgb(71 85 105 / var(--tw-bg-opacity));
@ -891,11 +873,6 @@ video {
color: rgb(165 180 252 / var(--tw-text-opacity)); 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 { .dark .dark\:hover\:bg-slate-900:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(15 23 42 / var(--tw-bg-opacity)); background-color: rgb(15 23 42 / var(--tw-bg-opacity));

@ -1,4 +1,5 @@
include .env 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 # Migrations should be written first and the model files can be created using this command
db_entities: db_entities:

Loading…
Cancel
Save