feat/Introduce config module

pull/7/head
Miroito 3 years ago
parent 9d96ce7925
commit 90b65bf6b2

@ -9,7 +9,6 @@ edition = "2021"
server = { version = "0.1.0", path = "./server" }
client = { version = "0.1.0", path = "./client" }
[workspace]
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(
company_slug: Option<String>,
page: i64,
size: i64,
) -> Result<PaginatedResponse<TransactionCompany>, ()> {
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::<PaginatedResponse<TransactionCompany>>()
.await
.unwrap();
.map_err(|_| ())?;
Ok(res)
}

@ -10,11 +10,17 @@ pub struct AsyncSelectRx<T>
where
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
{
pub remote_list: ReadSignal<String>,
pub selected_item: Signal<Option<T>>,
}
#[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
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
for<'de> T: Deserialize<'de>,
@ -31,7 +37,7 @@ where
let item_list: Signal<Vec<T>> = 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

@ -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<M>
#[derive(Clone)]
pub struct PaginatedTableStateRx<M, F, C>
where
M: 'static,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>>,
{
pub req: String,
pub ph_data: PhantomData<M>,
pub route: C,
pub filter: Option<String>,
}
#[component(PaginatedTable<G>)]
pub fn component<M>(PaginatedTableStateRx { req, ph_data }: PaginatedTableStateRx<M>) -> View<G>
impl<M, F, C> PaginatedTableStateRx<M, F, C>
where
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>,
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 table_prop: TableContentRx<G> = 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::<PaginatedResponse<M>>()
.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();

@ -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};
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<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 components;
mod env;
pub mod error_pages;
pub mod global_state;
pub mod templates;
@ -12,4 +14,15 @@ pub fn main<G: Html>() -> PerseusApp<G> {
.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()
}
}
})
}

@ -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<G> {
pub fn index_page(
IndexPageStateRx { company_slug }: IndexPageStateRx,
global_state: AppStateRx,
) -> View<G> {
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<TransactionCompany> = PaginatedTableStateRx {
req,
ph_data: Signal::new(PhantomData),
};
let paginated_table_state: PaginatedTableStateRx<TransactionCompany, _, _> =
PaginatedTableStateRx {
route: get_transactions,
filter: if (*company_slug.get()).is_empty() {
None
} else {
Some((*company_slug.get()).clone())
},
};
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),
};
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<IndexPageState> {
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<Vec<String>> {

@ -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));

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

Loading…
Cancel
Save