parent
3976e8ff9b
commit
34ea39bfe0
@ -0,0 +1,21 @@
|
|||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Prop)]
|
||||||
|
pub struct MainProps<'a, G: Html> {
|
||||||
|
children: Children<'a, G>,
|
||||||
|
useless_prop: u8, // Without another prop, the view doesn't render with children
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This component is used to contain the main contents of pages
|
||||||
|
#[component]
|
||||||
|
pub fn MainContentContainer<'a, G: Html>(cx: Scope<'a>, props: MainProps<'a, G>) -> View<G> {
|
||||||
|
let children = props.children.call(cx);
|
||||||
|
|
||||||
|
view! {cx,
|
||||||
|
div (id="main", class="flex flex-col items-center justify-center ") {
|
||||||
|
div (class="w-4/5 m-10 p-3 bg-slate-100 dark:bg-slate-600 rounded-lg items-center justify-center") {
|
||||||
|
(children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +1,48 @@
|
|||||||
|
use perseus::prelude::*;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
use crate::global_state::AppStateRx;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn the_header<G: Html>(cx: Scope) -> View<G> {
|
pub fn TheHeader<'a, G: Html>(cx: Scope<'a>) -> View<G> {
|
||||||
|
// This is ugly and is only caused by the get_global_state function panicking when running on the server at build time
|
||||||
|
let global_state_sig: &Signal<Option<&AppStateRx>> = create_signal(cx, None);
|
||||||
|
|
||||||
|
#[cfg(client)]
|
||||||
|
global_state_sig.set(Some(
|
||||||
|
Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx),
|
||||||
|
));
|
||||||
|
|
||||||
|
let dark_mode = create_signal(cx, true);
|
||||||
|
create_effect(cx, move || {
|
||||||
|
if let Some(gstate) = (*global_state_sig.get()).clone() {
|
||||||
|
dark_mode.set(*gstate.dark_mode.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let toggle_dark_mode = move |_| {
|
||||||
|
if let Some(gstate) = *global_state_sig.get() {
|
||||||
|
gstate.dark_mode.set(!*dark_mode.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view! { cx,
|
view! { cx,
|
||||||
"Don't use until global state is recheable from a component"
|
header (class="shadow-md h-11 p-2 align-middle w-full bg-gray-100 dark:bg-slate-500/30 backdrop-blur-lg") {
|
||||||
// header (class="shadow-md h-10 sm:p-2 w-full mb-20 bg-gray-100 dark:bg-slate-500/30 backdrop-blur-lg") {
|
div (class="flex") {
|
||||||
// nav () {
|
div (class="flex-none mr-12") {
|
||||||
// div (class="fixed flex justify-between") {
|
a (href="/", class="hover:underline") {
|
||||||
// div (class="font-mono mr-10") { "Fast Insiders" }
|
"Fast Insiders"
|
||||||
// div (class="flex justify-end") {
|
}
|
||||||
// button (on:click=toggle_dark_mode, class="mx-1 p-1 bg-pink-200 dark:bg-pink-600 rounded-full") { "toggle dark mode" }
|
}
|
||||||
// }
|
div (class="grow text-left") {
|
||||||
// }
|
a (id="header-all-transactions", href="/transactions", class="hover:underline") {
|
||||||
// }
|
"All transactions"
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
div (class="flex-none") {
|
||||||
|
button (on:click=toggle_dark_mode, class="mx-1 py-1 px-2 bg-slate-200 dark:bg-slate-800 rounded-full")
|
||||||
|
{ "Toggle dark mode" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod transactions;
|
||||||
|
|||||||
@ -0,0 +1,136 @@
|
|||||||
|
use perseus::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::routes::transaction::get_transactions,
|
||||||
|
api::types::{company::Company, transaction::TransactionCompany},
|
||||||
|
components::{
|
||||||
|
base_async_select::{AsyncSelectRx, BaseAsyncSelect},
|
||||||
|
base_button::{BaseButton, BaseButtonStateRx},
|
||||||
|
main_content_container::MainContentContainer,
|
||||||
|
paginated_data_table::{PaginatedTable, PaginatedTableStateRx},
|
||||||
|
the_header::TheHeader,
|
||||||
|
},
|
||||||
|
global_state::AppStateRx,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, ReactiveState)]
|
||||||
|
#[rx(alias = "TransactionsPageStateRx")]
|
||||||
|
pub struct TransactionsPageState {
|
||||||
|
pub company_slug: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[auto_scope]
|
||||||
|
fn transactions_page<G: Html>(cx: Scope, state: &TransactionsPageStateRx) -> View<G> {
|
||||||
|
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
|
||||||
|
let dark_mode = &global_state.dark_mode;
|
||||||
|
|
||||||
|
let expand = create_signal(cx, false);
|
||||||
|
let filter_expand = BaseButtonStateRx {
|
||||||
|
label: create_signal(cx, "Filters".to_string()),
|
||||||
|
disabled: create_signal(cx, false),
|
||||||
|
clicked: create_signal(cx, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
create_effect(cx, move || {
|
||||||
|
if *filter_expand.clicked.get() {
|
||||||
|
filter_expand.clicked.set(false);
|
||||||
|
expand.set(!*expand.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let paginated_table_state: PaginatedTableStateRx<TransactionCompany, _, _> =
|
||||||
|
PaginatedTableStateRx {
|
||||||
|
record_label: "transactions".to_owned(),
|
||||||
|
route: get_transactions,
|
||||||
|
filter: (*state.company_slug.get()).clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let async_select_prop: AsyncSelectRx<Company> = AsyncSelectRx {
|
||||||
|
remote_list: create_signal(cx, format!("{}company/", "http://localhost:8000/v1/")),
|
||||||
|
selected_item: create_signal(cx, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_button = BaseButtonStateRx {
|
||||||
|
label: create_signal(cx, "Search".to_string()),
|
||||||
|
disabled: create_memo(cx, move || async_select_prop.selected_item.get().is_none()),
|
||||||
|
clicked: create_signal(cx, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
create_effect(cx, || {
|
||||||
|
if *search_button.clicked.get() {
|
||||||
|
search_button.clicked.set(false);
|
||||||
|
navigate(&format!(
|
||||||
|
"/transactions/{}",
|
||||||
|
(*async_select_prop.selected_item.get())
|
||||||
|
.clone()
|
||||||
|
.map_or("".to_string(), |c| c.slug)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {cx,
|
||||||
|
main (class=if *dark_mode.get() { "dark" } else { "" }) {
|
||||||
|
div (class="bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-100 font-sans") {
|
||||||
|
TheHeader()
|
||||||
|
MainContentContainer(useless_prop=1) {
|
||||||
|
a (class="hover:underline", href="/transactions") {
|
||||||
|
h1 (
|
||||||
|
class="text-center text-lg"
|
||||||
|
) {
|
||||||
|
"Insider Transactions published by the AMF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BaseButton(filter_expand)
|
||||||
|
div (id="filters",
|
||||||
|
class=format!("p-2 border rounded-lg border-slate-200 dark:border-slate-800 bg-slate-200 dark:bg-slate-700 transition-all ease-in {}",
|
||||||
|
if *expand.get() { "h-40 visible" } else { "h-0 collapse" },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
div (class="w-80") {
|
||||||
|
p () {"Search for a company:"}
|
||||||
|
BaseAsyncSelect(async_select_prop)
|
||||||
|
BaseButton(search_button)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PaginatedTable(paginated_table_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_template<G: Html>() -> Template<G> {
|
||||||
|
Template::build("transactions")
|
||||||
|
.head(head)
|
||||||
|
.build_state_fn(get_build_state)
|
||||||
|
.build_paths_fn(get_build_paths)
|
||||||
|
.incremental_generation()
|
||||||
|
.view_with_state(transactions_page)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[engine_only_fn]
|
||||||
|
fn head(cx: Scope) -> View<SsrNode> {
|
||||||
|
view! {cx,
|
||||||
|
title { "Fast Insiders" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[engine_only_fn]
|
||||||
|
async fn get_build_state(
|
||||||
|
StateGeneratorInfo { path, .. }: StateGeneratorInfo<()>,
|
||||||
|
) -> Result<TransactionsPageState, BlamedError<std::io::Error>> {
|
||||||
|
let company_slug = if path.is_empty() { None } else { Some(path) };
|
||||||
|
Ok(TransactionsPageState { company_slug })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[engine_only_fn]
|
||||||
|
async fn get_build_paths() -> BuildPaths {
|
||||||
|
BuildPaths {
|
||||||
|
paths: vec!["".to_string()],
|
||||||
|
extra: ().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue