Upgraded Leptos to 0.8. Fixes #1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 07:51:29 +02:00
co-authored by Claude Opus 5.5
parent 01d48ac0e4
commit ec73351405
55 changed files with 1371 additions and 1097 deletions
+23 -19
View File
@@ -1,9 +1,11 @@
use crate::locales::init_locales;
use crate::pages::home_page::HomePage;
use crate::pages::settings::Settings;
use leptos::*;
use leptos::prelude::*;
use leptos_meta::*;
use leptos_router::*;
use leptos_router::components::{Route, Router, Routes};
use leptos_router::hooks::use_location;
use leptos_router::path;
use crate::components::admin_portal::AdminPortal;
use crate::components::header::Header;
use crate::components::user_menu::MenuOpener;
@@ -14,29 +16,31 @@ use crate::pages::login::Login;
use crate::pages::mail_settings::MailSettings;
use crate::pages::public::Public;
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct MenuHelper {
opened: RwSignal<MenuOpener>
opened: RwSignal<Option<MenuOpener>>
}
impl MenuHelper {
pub fn new() -> Self {
let opened = create_rw_signal(MenuOpener::new());
let opened = RwSignal::new(None);
Self {
opened
}
}
pub fn set_opened(&self, opened: MenuOpener) {
self.opened.set(opened);
self.opened.set(Some(opened));
}
pub fn close(&self) {
self.opened.get().close();
if let Some(opened) = self.opened.get_untracked() {
opened.close();
}
}
pub fn reset(&self) {
self.opened.set(MenuOpener::new());
self.opened.set(None);
}
}
@@ -47,7 +51,7 @@ pub struct DialogHelper {
impl DialogHelper {
pub fn new() -> Self {
let opened = create_rw_signal(false);
let opened = RwSignal::new(false);
Self {
opened
}
@@ -68,7 +72,7 @@ fn app_footer() -> impl IntoView {
view! {
<footer class="content-footer footer bg-footer-theme" style={move || if loc.pathname.get().starts_with("/admin") {"display: none;"} else {"display: block;"}}>
<div class="mb-2 mb-md-0" align="center">
<div class="mb-2 mb-md-0" style="text-align: center">
<a href="https://rezervovator.cz" target="_blank"><img src="/rezervovator_l.svg" width="110"/></a> {format!(" v {}", env!("CARGO_PKG_VERSION"))}
</div>
</footer>
@@ -89,35 +93,35 @@ pub fn App() -> impl IntoView {
<Router>
<main>
<Routes>
<Route path="" view=|| view! { <Public/> }/>
<Route path="login" view=|| view! { <Login/> }/>
<Route path="admin" view=|| view! {
<Routes fallback=|| "Page not found.">
<Route path=path!("") view=|| view! { <Public/> }/>
<Route path=path!("login") view=|| view! { <Login/> }/>
<Route path=path!("admin") view=|| view! {
<AdminPortal>
<HomePage/>
</AdminPortal>
}/>
<Route path="admin/settings" view=|| view! {
<Route path=path!("admin/settings") view=|| view! {
<AdminPortal>
<Settings/>
</AdminPortal>
}/>
<Route path="admin/mail_settings" view=|| view! {
<Route path=path!("admin/mail_settings") view=|| view! {
<AdminPortal>
<MailSettings/>
</AdminPortal>
}/>
<Route path="admin/bookings" view=|| view! {
<Route path=path!("admin/bookings") view=|| view! {
<AdminPortal>
<Bookings/>
</AdminPortal>
}/>
<Route path="admin/customers" view=|| view! {
<Route path=path!("admin/customers") view=|| view! {
<AdminPortal>
<Customers/>
</AdminPortal>
}/>
<Route path="admin/appearance" view=|| view! {
<Route path=path!("admin/appearance") view=|| view! {
<AdminPortal>
<Appearance/>
</AdminPortal>
+1 -1
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, Appearance};
use crate::components::data_form::ForValidation;
+3 -3
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, Company};
use crate::components::data_form::ForValidation;
@@ -25,7 +25,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
}
}}
#[server(GetCompany, "/api", "Url", "get_company")]
#[server(name = GetCompany, prefix = "/api", endpoint = "get_company")]
pub async fn get_company() -> Result<ApiResponse<Company>, ServerFnError> {
use crate::perm_check;
@@ -40,7 +40,7 @@ pub async fn get_company() -> Result<ApiResponse<Company>, ServerFnError> {
Ok(ApiResponse::Data(cmp))
}
#[server(UpdateCompany, "/api", "Url", "update_company")]
#[server(name = UpdateCompany, prefix = "/api", endpoint = "update_company")]
pub async fn update_company(company: Company) -> Result<ApiResponse<()>, ServerFnError> {
use crate::perm_check;
+1 -2
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::{server, ServerFnError};
use leptos::prelude::*;
use crate::backend::data::ApiResponse;
use crate::backend::data::Customer;
@@ -8,7 +8,6 @@ cfg_if! { if #[cfg(feature = "ssr")] {
use sqlx::{query_as, query};
use sqlx::Error;
use std::ops::DerefMut;
use leptos::expect_context;
use leptos_actix::extract;
use actix_web::cookie::Cookie;
use actix_web::cookie::SameSite;
+1 -1
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, Message, MessageType};
use crate::components::data_form::ForValidation;
+1 -1
View File
@@ -54,7 +54,7 @@ cfg_if!{
use sqlx::PgPool;
use actix_web::web::Data;
use leptos_actix::extract;
use leptos::ServerFnError;
use leptos::prelude::ServerFnError;
use crate::backend::mail::Mailer;
#[derive(Clone)]
+1 -1
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use cfg_if::cfg_if;
use chrono::Weekday;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, ClosingTime, DayHour, WeekHours};
use crate::components::data_form::ForValidation;
+1 -1
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, ResProperty};
use crate::components::data_form::ForValidation;
+1 -1
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData, ResSumWithItems, Page, PagedResponse};
use crate::components::data_form::ForValidation;
+2 -2
View File
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, PwdChange, User, UserProfile};
use crate::components::data_form::ForValidation;
@@ -157,7 +157,7 @@ pub async fn get_user() -> Result<Option<User>, ServerFnError> {
Ok(logged_in_user().await)
}
#[server(GetUsers, "/api", "Url", "get_users")]
#[server(name = GetUsers, prefix = "/api", endpoint = "get_users")]
pub async fn get_users() -> Result<ApiResponse<Vec<User>>, ServerFnError> {
use crate::perm_check;
use crate::backend::get_pool;
+4 -4
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::User;
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog};
use crate::components::user_menu::{MenuOpener, UserMenu};
@@ -12,7 +12,7 @@ fn about(opener: DialogOpener) -> impl IntoView {
<ModalDialog opener=opener title="">
<ModalBody>
<img src="/rezervovator_l.svg" width="180"/> <br /><br/>
<p>
<div>
{trl("Online booking application for sports facilities and service providers.")}<br/>
{format!(" v {}", env!("CARGO_PKG_VERSION"))}<br/><br/>
<div align="center">
@@ -20,7 +20,7 @@ fn about(opener: DialogOpener) -> impl IntoView {
<a href="https://leptos.dev" target="_blank"><img src="/Leptos_logo.png" height="40"/></a> <br/><br/>
"(c) 2023 - 2025"
</div>
</p>
</div>
</ModalBody>
</ModalDialog>
}
@@ -66,7 +66,7 @@ fn settings_menu(opener: MenuOpener) -> impl IntoView {
pub fn AdminPortal(children: Children) -> impl IntoView {
let user_menu = MenuOpener::new();
let settings_menu = MenuOpener::new();
let (user, set_user) = create_signal(User::default());
let (user, set_user) = signal(User::default());
let editor = DialogOpener::new();
let pw_changer = DialogOpener::new();
let drawer = use_context::<MenuOpener>().expect("No drawer opener");
+33 -31
View File
@@ -1,11 +1,12 @@
use crate::backend::data::ApiResponse;
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog, ModalFooter};
use leptos::*;
use leptos::server_fn::client::browser::BrowserClient;
use leptos::server_fn::codec::{Json, PostUrl};
use leptos::server_fn::error::NoCustomError;
use leptos::server_fn::ServerFn;
use leptos_router::*;
use leptos::prelude::*;
use leptos::form::{ActionForm, FromFormData};
use leptos::ev::SubmitEvent;
use leptos::server_fn::client::Client;
use leptos::server_fn::codec::PostUrl;
use leptos::server_fn::request::ClientReq;
use leptos::server_fn::{Http, ServerFn};
use serde::de::DeserializeOwned;
use validator::Validate;
use crate::components::server_err::ServerErr;
@@ -18,27 +19,28 @@ pub trait ForValidation {
}
#[component]
pub fn data_form<T: 'static + Clone +
ServerFn<Client = BrowserClient,
InputEncoding = PostUrl,
OutputEncoding = Json,
Error = NoCustomError,
Output = ApiResponse<()>> +
DeserializeOwned + ForValidation>(
pub fn data_form<T, O>(
opener: DialogOpener,
action: Action<T, Result<ApiResponse<()>, ServerFnError>>,
action: ServerAction<T>,
title: &'static str,
children: Children
) -> impl IntoView {
let upd_val = action.value();
) -> impl IntoView
where
T: ServerFn<Protocol = Http<PostUrl, O>, Output = ApiResponse<()>, Error = ServerFnError>
+ DeserializeOwned + ForValidation + Clone + Send + Sync + 'static,
T::Client: Client<ServerFnError>,
<<T::Client as Client<ServerFnError>>::Request as ClientReq<ServerFnError>>::FormData: From<web_sys::FormData>,
O: 'static
{
let upd_val = Signal::derive(move || action.value().get());
let validator = Validator::new();
view! {
<ActionForm
on:submit=move |ev| {
let act = T::from_event(&ev);
if !act.is_err() {
validator.check(act.unwrap().entity(), &ev);
// Capture phase, so the validation runs before the form is dispatched
on:submit:capture=move |ev: SubmitEvent| {
if let Ok(act) = T::from_event(&ev) {
validator.check(act.entity(), &ev);
}
}
action=action>
@@ -65,20 +67,20 @@ pub fn data_form<T: 'static + Clone +
}
#[component]
pub fn question_dialog<T: 'static
+ ServerFn<Client = BrowserClient,
InputEncoding = PostUrl,
OutputEncoding = Json,
Error = NoCustomError,
Output = ApiResponse<()>>
+ DeserializeOwned
+ Clone>(
pub fn question_dialog<T, O>(
opener: DialogOpener,
action: Action<T, Result<ApiResponse<()>, ServerFnError>>,
action: ServerAction<T>,
title: &'static str,
children: Children
) -> impl IntoView {
let upd_val = action.value();
) -> impl IntoView
where
T: ServerFn<Protocol = Http<PostUrl, O>, Output = ApiResponse<()>, Error = ServerFnError>
+ DeserializeOwned + Clone + Send + Sync + 'static,
T::Client: Client<ServerFnError>,
<<T::Client as Client<ServerFnError>>::Request as ClientReq<ServerFnError>>::FormData: From<web_sys::FormData>,
O: 'static
{
let upd_val = Signal::derive(move || action.value().get());
view! {
<ActionForm action=action>
+15 -14
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use leptos_meta::*;
use crate::app::DialogHelper;
use crate::backend::appearance::get_appearance;
@@ -8,18 +8,18 @@ use crate::components::user_menu::MenuOpener;
pub fn Header() -> impl IntoView {
let drawer = use_context::<MenuOpener>().expect("No drawer opener");
let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper");
let appearance = create_blocking_resource(||(), |_| get_appearance());
let appearance = Resource::new_blocking(||(), |_| get_appearance());
view! {
<Html
{..}
lang="cz"
dir="ltr"
class = {move || if drawer.visible() {"light-style layout-menu-fixed layout-menu-expanded"}
else {"light-style layout-menu-fixed"} }
attributes=vec![
("data-theme", "theme-default".into_attribute()),
("data-template", "vertical-menu-template-free".into_attribute()),
("data-assets-path", "/".into_attribute())]
data-theme="theme-default"
data-template="vertical-menu-template-free"
data-assets-path="/"
/>
<Title text="Rezervovator"/>
<Meta charset="utf-8"/>
@@ -41,19 +41,20 @@ pub fn Header() -> impl IntoView {
<Link rel="stylesheet" href="/vendor/css/theme-default.css" />
<Link rel="stylesheet" href="/css/demo.css" />
<Transition fallback=move || view! {""}>
{
appearance.get().map(|a| match a {
Ok(a) => view! {<Link rel="stylesheet" href={format!("/data/{}", a.css_name.unwrap_or_default())} />},
Err(_) => view! {<Link rel="stylesheet" href="/data/banner.css" />}
{move || {
appearance.get().map(|a| {
let css_name = match a {
Ok(a) => a.css_name.unwrap_or_default(),
Err(_) => "banner.css".to_string()
};
view! {<Link rel="stylesheet" href={format!("/data/{}", css_name)} />}
})
}
}}
</Transition>
<Link rel="stylesheet" href="/vendor/css/control.css" />
//<!-- Vendors CSS -->
<Link rel="stylesheet" href="/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
<Body attributes=vec![
("style", {move || if dlg_helper.is_open() {"overflow: hidden;"} else {""}}.into_attribute())
]/>
<Body {..} style={move || if dlg_helper.is_open() {"overflow: hidden;"} else {""}}/>
}
}
+18 -13
View File
@@ -1,5 +1,5 @@
use crate::locales::trl;
use leptos::*;
use leptos::prelude::*;
use crate::app::DialogHelper;
use crate::validator::Validator;
@@ -11,15 +11,17 @@ pub struct DialogOpener {
set_empty: WriteSignal<String>,
not_checked: ReadSignal<Option<String>>,
set_not_checked: WriteSignal<Option<String>>,
show_err: RwSignal<bool>
show_err: RwSignal<bool>,
// Context is not available in event handlers, so the helper is looked up when the opener is created
dlg_helper: Option<DialogHelper>
}
impl DialogOpener {
pub fn new() -> Self {
let (visible, set_visible) = create_signal(false);
let (empty, set_empty) = create_signal("".to_string());
let (not_checked, set_not_checked) = create_signal(None);
let show_err = create_rw_signal(false);
let (visible, set_visible) = signal(false);
let (empty, set_empty) = signal("".to_string());
let (not_checked, set_not_checked) = signal(None);
let show_err = RwSignal::new(false);
DialogOpener {
visible,
set_visible,
@@ -27,7 +29,8 @@ impl DialogOpener {
set_empty,
not_checked,
set_not_checked,
show_err
show_err,
dlg_helper: use_context::<DialogHelper>()
}
}
@@ -36,15 +39,17 @@ impl DialogOpener {
}
pub fn show(&self) {
let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper");
dlg_helper.set_opened(true);
self.set_visible.update(|state| *state = true);
if let Some(dlg_helper) = self.dlg_helper {
dlg_helper.set_opened(true);
}
self.set_visible.set(true);
}
pub fn hide(&self) {
let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper");
dlg_helper.set_opened(false);
self.set_visible.update(|state| *state = false);
if let Some(dlg_helper) = self.dlg_helper {
dlg_helper.set_opened(false);
}
self.set_visible.set(false);
self.set_empty.set("".to_string());
self.set_not_checked.set(None);
self.show_err.set(false);
+7 -6
View File
@@ -1,11 +1,12 @@
use crate::components::modal_box::DialogOpener;
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::ApiResponse;
use crate::locales::trl;
#[component]
pub fn ServerErr(
result: RwSignal<Option<Result<ApiResponse<()>, ServerFnError>>>,
#[prop(into)]
result: Signal<Option<Result<ApiResponse<()>, ServerFnError>>>,
opener: DialogOpener,
) -> impl IntoView {
view! {{move || {
@@ -23,22 +24,22 @@ pub fn ServerErr(
}}>
{trl(&err)}
</div>
}
}.into_any()
}
else {
opener.hide();
view! {<div></div>}
view! {<div></div>}.into_any()
}
Err(e) => {
view! {
<div class="alert alert-danger">
"Server error: " {e.to_string()}
</div>
}
}.into_any()
}
}
} else {
view! {<div></div>}
view! {<div></div>}.into_any()
}
}}
}
+21 -16
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::app::MenuHelper;
use crate::backend::data::User;
use crate::backend::user::{get_user, logout};
@@ -9,14 +9,17 @@ use crate::locales::trl;
pub struct MenuOpener {
visible: ReadSignal<bool>,
set_visible: WriteSignal<bool>,
// Context is not available in event handlers, so the helper is looked up when the opener is created
helper: Option<MenuHelper>,
}
impl MenuOpener {
pub fn new() -> Self {
let (visible, set_visible) = create_signal(false);
let (visible, set_visible) = signal(false);
MenuOpener {
visible,
set_visible,
helper: use_context::<MenuHelper>(),
}
}
@@ -25,22 +28,24 @@ impl MenuOpener {
}
pub fn toggle(self) {
let visible = self.visible.get();
self.set_visible.update(|v| *v = !visible);
let visible = self.visible.get_untracked();
self.set_visible.set(!visible);
let helper = use_context::<MenuHelper>().expect("No menu helper");
if !visible {
helper.close();
helper.set_opened(self);
} else {
helper.reset();
if let Some(helper) = self.helper {
if !visible {
helper.close();
helper.set_opened(self);
} else {
helper.reset();
}
}
}
pub fn close(&self) {
self.set_visible.set(false);
let helper = use_context::<MenuHelper>().expect("No menu helper");
helper.reset();
if let Some(helper) = self.helper {
helper.reset();
}
}
}
@@ -50,7 +55,7 @@ pub fn UserMenu(
editor: DialogOpener,
pw_dialog: DialogOpener,
user_profile: WriteSignal<User>) -> impl IntoView {
let user = create_resource(move || opener.visible(), move |_| get_user());
let user = Resource::new(move || opener.visible(), move |_| get_user());
view! {
<ul
@@ -84,13 +89,13 @@ pub fn UserMenu(
<span class="fw-semibold d-block">
{usr.full_name}
</span>
}
}.into_any()
}
Err(_) => {
view! {
// <small class="text-muted">"Admin"</small>
<span>"Error loading user"</span>
}
}.into_any()
}
})
}}
@@ -114,7 +119,7 @@ pub fn UserMenu(
class="dropdown-item"
href="/login"
on:click=move |_| {
spawn_local(async move {
leptos::task::spawn_local(async move {
let _ = logout().await;
});
}
+4 -4
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::locales::trl;
use crate::validator::Validator;
@@ -20,16 +20,16 @@ pub fn ValidationErr(
<div>
{out_msgs}
</div>
}
}.into_any()
} else {
view! {
<div class="alert alert-danger">
{trl("Validation error")}
</div>
}
}.into_any()
}
} else {
view! {<div></div>}
view! {<div></div>}.into_any()
}
}}
}
+1 -1
View File
@@ -1,7 +1,7 @@
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use chrono::ParseError;
use leptos::ServerFnError;
use leptos::prelude::ServerFnError;
use log::error;
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+9 -17
View File
@@ -1,3 +1,6 @@
// Leptos view types are deeply nested; release builds overflow the default limit
#![recursion_limit = "512"]
pub mod app;
pub mod locales;
pub mod backend;
@@ -7,23 +10,12 @@ mod validator;
pub mod error;
pub mod config;
use cfg_if::cfg_if;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use crate::app::*;
cfg_if! {
if #[cfg(feature = "hydrate")] {
console_error_panic_hook::set_once();
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
use app::*;
use leptos::*;
console_error_panic_hook::set_once();
leptos::mount_to_body(move || {
view! { <App/> }
});
}
}
leptos::mount::hydrate_body(App);
}
+5 -6
View File
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use leptos::*;
use leptos::prelude::*;
use crate::locales::Locales;
lazy_static! {
@@ -182,16 +182,15 @@ lazy_static! {
};
}
fn get_locales() -> Vec<Option<String>> {
pub fn get_locales() -> Vec<Option<String>> {
#[cfg(not(feature = "ssr"))]
{
use_context::<Locales>().unwrap_or(Locales(vec![])).0
// Context is not available in event handlers, ask the browser there
use_context::<Locales>().unwrap_or_else(crate::locales::browser_locales).0
}
#[cfg(feature = "ssr")]
{
use actix_web::HttpRequest;
let req = use_context::<HttpRequest>();
let req = use_context::<leptos_actix::Request>();
if let Some(r) = req {
if let Some(al) = r.head().headers.get("accept-language") {
Locales(al.to_str().unwrap_or("").split(",").map(|l| Some(l.to_string())).collect()).0
+18 -18
View File
@@ -1,6 +1,6 @@
use chrono::{NaiveDate, Weekday};
use leptos::use_context;
use crate::locales::catalogues::get_dictionary;
use leptos::prelude::*;
use crate::locales::catalogues::{get_dictionary, get_locales};
mod catalogues;
@@ -10,24 +10,12 @@ pub struct Locales(pub Vec<Option<String>>);
pub fn init_locales() {
#[cfg(not(feature = "ssr"))]
{
use leptos::*;
let loc = Locales(
window()
.navigator()
.languages()
.into_iter()
.map(|val| val.as_string())
.collect::<Vec<_>>(),
);
provide_context(loc);
provide_context(browser_locales());
}
#[cfg(feature = "ssr")]
{
use actix_web::HttpRequest;
use leptos::provide_context;
let req = use_context::<HttpRequest>();
let req = use_context::<leptos_actix::Request>();
if let Some(r) = req {
if r.query_string().contains("lang") {
let languages = r.query_string()
@@ -49,6 +37,19 @@ pub fn init_locales() {
}
}
/// Languages preferred by the browser
#[cfg(not(feature = "ssr"))]
pub fn browser_locales() -> Locales {
Locales(
window()
.navigator()
.languages()
.into_iter()
.map(|val| val.as_string())
.collect::<Vec<_>>(),
)
}
pub fn trl(phrase: &str) -> impl Fn() -> String {
let mut translated = phrase;
if let Some(dict) = get_dictionary() {
@@ -65,8 +66,7 @@ pub fn trl(phrase: &str) -> impl Fn() -> String {
// ToDo better date formatting
pub fn loc_date(date: NaiveDate) -> impl Fn() -> String {
let mut dt = date.format("%Y-%m-%d").to_string();
let locs = use_context::<Locales>().unwrap_or(Locales(vec![])).0;
for loc in locs {
for loc in get_locales() {
if let Some(key) = loc {
if let Some(k) = key.split("-").collect::<Vec<_>>().get(0) {
if *k != "en" {
+32 -19
View File
@@ -1,3 +1,6 @@
// Leptos view types are deeply nested; release builds overflow the default limit
#![recursion_limit = "512"]
use actix_web::middleware::Logger;
use leptos_captcha::spow::pow::Pow;
use log::error;
@@ -11,7 +14,9 @@ use rezervator::backend::user::create_admin;
async fn main() -> std::io::Result<()> {
use actix_files::Files;
use actix_web::*;
use leptos::*;
use leptos::prelude::*;
use leptos::config::get_configuration;
use leptos_meta::MetaTags;
use leptos_actix::{generate_route_list, LeptosRoutes};
use rezervator::app::*;
use rezervator::backend::*;
@@ -48,9 +53,7 @@ async fn main() -> std::io::Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or(srv_conf.logging().severity())).init();
info!("Starting server");
let conf = get_configuration(None).await.unwrap();
// Generate the list of routes in your Leptos App
let routes = generate_route_list(|| view! { <App/> });
let conf = get_configuration(None).unwrap();
let key = Key::from(srv_conf.session().key());
let pool = PgPoolOptions::new()
@@ -75,8 +78,10 @@ async fn main() -> std::io::Result<()> {
}
HttpServer::new(move || {
// Generate the list of routes in your Leptos App
let routes = generate_route_list(App);
let leptos_options = &conf.leptos_options;
let site_root = &leptos_options.site_root;
let site_root = leptos_options.site_root.clone().to_string();
App::new()
.app_data(Data::new(AppData::new(pool.clone(), mailer.clone())))
@@ -86,12 +91,26 @@ async fn main() -> std::io::Result<()> {
CookieSessionStore::default(),
key.clone()
))
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.leptos_routes(
leptos_options.to_owned(),
routes.to_owned(),
|| view! { <App/> },
)
// Server functions under /api are registered by leptos_routes
.leptos_routes(routes, {
let leptos_options = leptos_options.clone();
move || {
view! {
<!DOCTYPE html>
<html>
<head>
<AutoReload options=leptos_options.clone()/>
<HydrationScripts options=leptos_options.clone()/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
})
.app_data(Data::new(leptos_options.to_owned()))
.service(appearance::upload)
.service(Files::new("/", site_root))
.wrap(middleware::Compress::default())
@@ -114,15 +133,9 @@ pub fn main() {
// a client-side main function is required for using `trunk serve`
// prefer using `cargo leptos serve` instead
// to run: `trunk serve --open --features ssg`
use leptos::*;
use leptos_start::app::*;
use wasm_bindgen::prelude::wasm_bindgen;
use rezervator::app::*;
console_error_panic_hook::set_once();
leptos::mount_to_body(move || {
// note: for testing it may be preferrable to replace this with a
// more specific component, although leptos_router should still work
view! {<App/> }
});
leptos::mount::mount_to_body(App);
}
+29 -35
View File
@@ -1,5 +1,5 @@
use chrono::{Datelike, Local};
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{ApiResponse, ReservationState, ResSumWithItems};
use crate::backend::reservation::{month_chart, reservations_in_month, year_chart, years};
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog};
@@ -11,7 +11,7 @@ use crate::pages::new_reservations::CancelDialog;
fn booking_detail(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener) -> impl IntoView {
let menu = MenuOpener::new();
let cancel_opener = DialogOpener::new();
let menu_visible = create_rw_signal(false);
let menu_visible = RwSignal::new(false);
view! {
<CancelDialog opener=cancel_opener reservation=reservation/>
<ModalDialog title="Booking detail" opener=opener>
@@ -27,7 +27,7 @@ fn booking_detail(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener
let:item>
{item.property.name}": "{item.reservation.from.to_string()}" - "{item.reservation.to.to_string()}<br/>
</For>
{trl("Customer: ")}{detail.customer.full_name}", "<a href={format!("mailto:{}", detail.customer.email)}>{detail.customer.email}</a>", "{detail.customer.phone}<br/>
{trl("Customer: ")}{detail.customer.full_name}", "<a href={format!("mailto:{}", detail.customer.email)}>{detail.customer.email.clone()}</a>", "{detail.customer.phone}<br/>
{
let note = detail.summary.note.clone();
let show = note.is_some() && !note.clone().unwrap().is_empty();
@@ -42,7 +42,7 @@ fn booking_detail(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener
}}
</p>
<Show when=move || menu_visible.get()>
<table width="100%">
<table style="width: 100%"><tbody>
<tr>
<td width="100%"></td>
<td>
@@ -62,7 +62,7 @@ fn booking_detail(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener
</div>
</td>
</tr>
</table>
</tbody></table>
</Show>
</ModalBody>
</ModalDialog>
@@ -71,15 +71,15 @@ fn booking_detail(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener
#[component]
pub fn bookings() -> impl IntoView {
let year_chart = create_blocking_resource(||(), |_| year_chart());
let years = create_blocking_resource(||(), |_| years());
let year = create_rw_signal(Local::now().year());
let month = create_rw_signal(Local::now().month());
let chart = create_blocking_resource(move || year.get(),move |y| month_chart(y));
let year_chart = Resource::new_blocking(||(), |_| year_chart());
let years = Resource::new_blocking(||(), |_| years());
let year = RwSignal::new(Local::now().year());
let month = RwSignal::new(Local::now().month());
let chart = Resource::new_blocking(move || year.get(),move |y| month_chart(y));
let detail_dlg = DialogOpener::new();
let reservations = create_blocking_resource(move || (year.get(), month.get(), detail_dlg.visible()), move |p| reservations_in_month(p.0, p.1));
let reservations = Resource::new_blocking(move || (year.get(), month.get(), detail_dlg.visible()), move |p| reservations_in_month(p.0, p.1));
let all_months: Vec<u32> = vec![1,2,3,4,5,6,7,8,9,10,11,12];
let res_detail = create_rw_signal(ResSumWithItems::default());
let res_detail = RwSignal::new(ResSumWithItems::default());
view! {
<h1>{trl("Booking overview")}</h1>
@@ -94,7 +94,7 @@ pub fn bookings() -> impl IntoView {
</li>
<li class="nav-item">
<Transition fallback=move || view! {<div>"Loading"</div>} >
{
{move || {
years.get().map(|y| match y {
Ok(y) => {
view! {
@@ -106,11 +106,11 @@ pub fn bookings() -> impl IntoView {
<option prop:value=move || y prop:selected=move || year.get() == y>{y}</option>
</For>
</select>
}
}.into_any()
}
Err(_) => {view! {<select><option></option></select>}}
Err(_) => {view! {<select><option></option></select>}.into_any()}
})
}
}}
</Transition>
</li>
<li class="nav-item">
@@ -135,30 +135,24 @@ pub fn bookings() -> impl IntoView {
<div class="col-md">
<div class="card md-3">
<Transition fallback=move || view! {<div>"Loading"</div>}>
{
{move || {
chart.get().map(move |c| match c {
Ok(c) => { match c {
ApiResponse::Data(c) => {view! {<img style="margin: 1em" src={c.clone()} /> }}
ApiResponse::Error(_) => {view! {<img src=""/> }} }
}
Err(_) => { view! {<img src=""/> } }
Ok(ApiResponse::Data(c)) => {view! {<img style="margin: 1em" src={c.clone()} /> }.into_any()}
_ => { view! {<img src=""/> }.into_any() }
})
}
}}
</Transition>
</div>
</div>
<div class="col-md">
<div class="card md-3">
<Transition fallback=move || view! {<div>"Loading"</div>}>
{
{move || {
year_chart.get().map(move |c| match c {
Ok(c) => { match c {
ApiResponse::Data(c) => {view! {<img style="margin: 1em" src={c.clone()} /> }}
ApiResponse::Error(_) => {view! {<img src=""/> }} }
}
Err(_) => { view! {<img src=""/> } }
Ok(ApiResponse::Data(c)) => {view! {<img style="margin: 1em" src={c.clone()} /> }.into_any()}
_ => { view! {<img src=""/> }.into_any() }
})
}
}}
</Transition>
</div>
</div>
@@ -178,7 +172,7 @@ pub fn bookings() -> impl IntoView {
<th>{trl("Actions")}</th>
</tr>
</thead>
{
{move || {
reservations.get().map(|r| match r {
Ok(r) => { match r {
ApiResponse::Data(r) => {
@@ -202,20 +196,20 @@ pub fn bookings() -> impl IntoView {
</tr>
</For>
</tbody>
}
}.into_any()
}
ApiResponse::Error(e) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}.into_any()
}
}
}
Err(e) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}.into_any()
}
})
}
}}
</table>
</Transition>
</div>
+16 -15
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::appearance::{DeleteBanner, get_appearance, UpdateAppearance};
use crate::backend::data::Appearance;
use crate::components::data_form::{DataForm, QuestionDialog};
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
fn edit_title(opener: DialogOpener, appearance: ReadSignal<Appearance>) -> impl IntoView {
let update = create_server_action::<UpdateAppearance>();
let update = ServerAction::<UpdateAppearance>::new();
view! {
<DataForm opener=opener action=update title="Update title">
<input type="hidden" prop:value={move || appearance.get().text.unwrap_or_default()} name="appearance[text]" />
@@ -31,7 +31,7 @@ fn edit_title(opener: DialogOpener, appearance: ReadSignal<Appearance>) -> impl
#[component]
fn edit_text(opener: DialogOpener, appearance: ReadSignal<Appearance>) -> impl IntoView {
let update = create_server_action::<UpdateAppearance>();
let update = ServerAction::<UpdateAppearance>::new();
view! {
<DataForm opener=opener action=update title="Update text">
<input type="hidden" prop:value={move || appearance.get().title.unwrap_or_default()} name="appearance[title]" />
@@ -53,7 +53,7 @@ fn edit_text(opener: DialogOpener, appearance: ReadSignal<Appearance>) -> impl I
#[component]
fn delete_banner(opener: DialogOpener) -> impl IntoView {
let delete = create_server_action::<DeleteBanner>();
let delete = ServerAction::<DeleteBanner>::new();
view! {
<QuestionDialog opener=opener action=delete title="Delete banner">
<div>{trl("Are you sure you want to delete banner?")}</div>
@@ -66,8 +66,8 @@ pub fn appearance() -> impl IntoView {
let title_edit = DialogOpener::new();
let text_edit = DialogOpener::new();
let del_dialog = DialogOpener::new();
let appearance = create_resource(move || title_edit.visible() || text_edit.visible() || del_dialog.visible(), |_| get_appearance());
let app_edit = create_rw_signal(Appearance::default());
let appearance = Resource::new(move || title_edit.visible() || text_edit.visible() || del_dialog.visible(), |_| get_appearance());
let app_edit = RwSignal::new(Appearance::default());
view! {
<EditTitle opener=title_edit appearance=app_edit.read_only()/>
<EditText opener=text_edit appearance=app_edit.read_only()/>
@@ -85,16 +85,17 @@ pub fn appearance() -> impl IntoView {
</button>
</form><br/>
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{
{move || {
appearance.get().map(|a| match a {
Ok(a) => {
app_edit.set(a.clone());
let app = a.clone();
Ok(app) => {
app_edit.set(app.clone());
let has_banner = app.banner.is_some();
let title = app.title.clone().unwrap_or_default();
view! {
<div>
<Show when=move || a.clone().banner.is_some()>
<Show when=move || has_banner>
<div class="header_banner">
<h1 class="header_banner">{app.title.clone().unwrap_or_default()}</h1>
<h1 class="header_banner">{title.clone()}</h1>
</div>
</Show>
<div>
@@ -111,13 +112,13 @@ pub fn appearance() -> impl IntoView {
<i class="bx bx-edit-alt me-1"></i> {trl("Edit text")}</a>
</div>
</div>
}
}.into_any()
}
Err(e) => {view! {<div><p>{trl("Error loading data")}</p>
<p>{e.to_string()}</p></div>
}}
}.into_any()}
})
}
}}
</Transition>
</div>
+3 -3
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::User;
use crate::backend::user::{ChangePwd, get_user};
use crate::components::data_form::DataForm;
@@ -7,8 +7,8 @@ use crate::locales::trl;
#[component]
pub fn change_password(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
let change_pwd = create_server_action::<ChangePwd>();
let logged_in = create_resource(||(), move |_| get_user());
let change_pwd = ServerAction::<ChangePwd>::new();
let logged_in = Resource::new(||(), move |_| get_user());
view! {
<DataForm opener=opener action=change_pwd title="Change password">
+7 -7
View File
@@ -1,5 +1,5 @@
use chrono::Local;
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::ClosingTime;
use crate::backend::opening_hours::{get_closing_times, DeleteClosingTime, InsertClosingTime};
use crate::components::data_form::{DataForm, QuestionDialog};
@@ -8,7 +8,7 @@ use crate::locales::{loc_date, trl};
#[component]
pub fn del_closing_day(closing_time: ReadSignal<ClosingTime>, opener: DialogOpener) -> impl IntoView {
let delete = create_server_action::<DeleteClosingTime>();
let delete = ServerAction::<DeleteClosingTime>::new();
view! {
<QuestionDialog opener=opener action=delete title="Delete closing days">
@@ -20,7 +20,7 @@ pub fn del_closing_day(closing_time: ReadSignal<ClosingTime>, opener: DialogOpen
#[component]
pub fn insert_closing_days(opener: DialogOpener) -> impl IntoView {
let insert_day = create_server_action::<InsertClosingTime>();
let insert_day = ServerAction::<InsertClosingTime>::new();
view! {
<DataForm opener=opener action=insert_day title="Closing days">
@@ -52,8 +52,8 @@ pub fn insert_closing_days(opener: DialogOpener) -> impl IntoView {
pub fn closing_days() -> impl IntoView {
let delete_dialog = DialogOpener::new();
let editor = DialogOpener::new();
let times = create_blocking_resource(move || editor.visible() || delete_dialog.visible(), move |_| {get_closing_times()});
let time_to_del = create_rw_signal(ClosingTime::default());
let times = Resource::new_blocking(move || editor.visible() || delete_dialog.visible(), move |_| {get_closing_times()});
let time_to_del = RwSignal::new(ClosingTime::default());
view! {
<DelClosingDay closing_time=time_to_del.read_only() opener=delete_dialog/>
<InsertClosingDays opener=editor/>
@@ -78,7 +78,7 @@ pub fn closing_days() -> impl IntoView {
e.to_string()
};
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=3>{trl(&err)}</td></tr></tbody>}}
<tr><td colspan=3>{trl(&err)}</td></tr></tbody>}.into_any()}
Ok(c) => {
view! {<tbody class="table-border-bottom-0">
<For each=move || c.clone()
@@ -100,7 +100,7 @@ pub fn closing_days() -> impl IntoView {
</tr>
}
}/></tbody>
}
}.into_any()
}
})
}}
+2 -2
View File
@@ -1,7 +1,7 @@
use crate::backend::data::Company;
use crate::backend::company::UpdateCompany;
use crate::components::modal_box::DialogOpener;
use leptos::*;
use leptos::prelude::*;
use crate::components::data_form::DataForm;
use crate::locales::trl;
@@ -10,7 +10,7 @@ pub fn CompanyEdit(
company: ReadSignal<Company>,
opener: DialogOpener,
) -> impl IntoView {
let update_company = create_server_action::<UpdateCompany>();
let update_company = ServerAction::<UpdateCompany>::new();
view! {
<DataForm opener=opener action=update_company title="Edit company">
+8 -8
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::company::get_company;
use crate::backend::data::{ApiResponse, Company};
use crate::components::modal_box::DialogOpener;
@@ -8,21 +8,21 @@ use crate::pages::company_edit::CompanyEdit;
#[component]
pub fn CompanyInfo() -> impl IntoView {
let editor = DialogOpener::new();
let company = create_blocking_resource(move|| editor.visible(), move |_| { get_company() });
let (cmp, set_cmp) = create_signal(Company::default());
let company = Resource::new_blocking(move|| editor.visible(), move |_| { get_company() });
let (cmp, set_cmp) = signal(Company::default());
view! {
<CompanyEdit company={cmp} opener=editor/>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title"><i class="bx bx-buildings"></i>" "{trl("Company info")}</h5>
<p class="card-text">
<div class="card-text">
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{move || {
company.get().map(|c| match c {
Err(e) => {view! {<div><p>{trl("Error loading data")}</p>
<p>{e.to_string()}</p></div>
}}
}.into_any()}
Ok(c) => {
match c {
ApiResponse::Data(d) => {set_cmp.update(|cmp| *cmp = d.clone());
@@ -30,16 +30,16 @@ pub fn CompanyInfo() -> impl IntoView {
<p><b>{d.name}</b></p>
<p>{d.street}" "{d.house_number}<br/>
{d.zip_code}" "{d.city}
</p></div>}
</p></div>}.into_any()
}
ApiResponse::Error(_) => {view! {<div><p>{trl("Error loading data")}</p></div>}}
ApiResponse::Error(_) => {view! {<div><p>{trl("Error loading data")}</p></div>}.into_any()}
}
}
})
}}
</Transition>
</p>
</div>
<a href="javascript:void(0)" class="card-link" on:click = move |_| editor.show()>
<i class="bx bx-edit-alt fs-4 lh-0"></i></a>
</div>
+7 -7
View File
@@ -1,11 +1,11 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::customer::get_customers;
use crate::backend::data::ApiResponse;
use crate::locales::trl;
#[component]
pub fn customers() -> impl IntoView {
let customers = create_blocking_resource(||(), |_| get_customers());
let customers = Resource::new_blocking(||(), |_| get_customers());
view! {
<h1>{trl("Customers")}</h1>
@@ -21,7 +21,7 @@ pub fn customers() -> impl IntoView {
<th>{trl("Phone")}</th>
</tr>
</thead>
{
{move || {
customers.get().map(|c| match c {
Ok(c) => {match c {
ApiResponse::Data(c) => {
@@ -35,20 +35,20 @@ pub fn customers() -> impl IntoView {
</tr>
</For>
</tbody>
}
}.into_any()
}
ApiResponse::Error(e) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=3>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
<tr><td colspan=3>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}.into_any()
}
}
}
Err(e) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=3>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
<tr><td colspan=3>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}.into_any()
}
})
}
}}
</table>
</Transition>
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::components::modal_box::DialogOpener;
use crate::locales::trl;
use crate::pages::new_reservations::NewReservations;
+3 -3
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::WeekHours;
use crate::backend::opening_hours::{DeleteClosingTime, UpdateHours};
use crate::components::data_form::{DataForm, QuestionDialog};
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
pub fn EditHours(opener: DialogOpener, hours: ReadSignal<WeekHours>) -> impl IntoView {
let update_hours = create_server_action::<UpdateHours>();
let update_hours = ServerAction::<UpdateHours>::new();
view! {
<DataForm opener=opener action=update_hours title="Edit hours">
@@ -31,7 +31,7 @@ pub fn EditHours(opener: DialogOpener, hours: ReadSignal<WeekHours>) -> impl Int
#[component]
pub fn del_closing_days(opener: DialogOpener) -> impl IntoView {
let delete = create_server_action::<DeleteClosingTime>();
let delete = ServerAction::<DeleteClosingTime>::new();
view! {
<QuestionDialog opener=opener action=delete title="Delete closing days">
+4 -4
View File
@@ -1,6 +1,6 @@
use leptos::*;
use leptos::prelude::*;
use leptos_meta::*;
use leptos_router::ActionForm;
use leptos::form::ActionForm;
use crate::backend::user::Login;
use crate::components::modal_box::DialogOpener;
use crate::components::server_err::ServerErr;
@@ -8,8 +8,8 @@ use crate::locales::trl;
#[component]
pub fn Login() -> impl IntoView {
let login = create_server_action::<Login>();
let login_val = login.value();
let login = ServerAction::<Login>::new();
let login_val = Signal::derive(move || login.value().get());
view! {
<Link rel="stylesheet" href="/vendor/css/pages/page-auth.css" />
<div class="authentication-wrapper authentication-basic container-p-y">
+1 -1
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::MessageType;
use crate::locales::trl;
use crate::pages::mail_view::MailView;
+10 -10
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{Message, MessageType};
use crate::backend::mail::{get_message, UpdateMessage};
use crate::components::data_form::DataForm;
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
fn mail_edit(opener: DialogOpener, mail: ReadSignal<Message>) -> impl IntoView {
let update = create_server_action::<UpdateMessage>();
let update = ServerAction::<UpdateMessage>::new();
view! {
<DataForm opener=opener title="Edit mail" action=update>
@@ -44,17 +44,17 @@ fn mail_edit(opener: DialogOpener, mail: ReadSignal<Message>) -> impl IntoView {
#[component]
pub fn mail_view(title: &'static str, mail_type: MessageType) -> impl IntoView {
let editor = DialogOpener::new();
let mail = create_blocking_resource( move || editor.visible(), move |_| get_message(mail_type.clone()));
let for_edit = create_rw_signal(Message::default());
let mail = Resource::new_blocking( move || editor.visible(), move |_| get_message(mail_type.clone()));
let for_edit = RwSignal::new(Message::default());
view! {
<MailEdit opener=editor mail={for_edit.read_only()}/>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title"><i class="bx bx-buildings"></i>" "{trl(title)}</h5>
<p class="card-text">
<div class="card-text">
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{
{move || {
mail.get().map(|m| match m {
Ok(m) => {
for_edit.set(m.clone());
@@ -63,16 +63,16 @@ pub fn mail_view(title: &'static str, mail_type: MessageType) -> impl IntoView {
<p>{trl("Subject: ")}{m.subject}</p>
<p>{m.text}</p>
</div>
}
}.into_any()
}
Err(_) => {
view! {
<div>"Error loading data"</div>
}
}.into_any()
}})
}
}}
</Transition>
</p>
</div>
<a href="javascript:void(0)" class="card-link" on:click = move |_| editor.show()>
<i class="bx bx-edit-alt fs-4 lh-0"></i></a>
</div>
+15 -15
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{ApiResponse, Page, ResSumWithItems};
use crate::backend::reservation::{Approve, Cancel, get_new_reservations};
use crate::components::data_form::QuestionDialog;
@@ -10,7 +10,7 @@ use chrono::Datelike;
#[component]
fn approve_dialog(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener) -> impl IntoView {
let approve = create_server_action::<Approve>();
let approve = ServerAction::<Approve>::new();
view! {
<QuestionDialog opener=opener action=approve title="Approve booking">
@@ -27,7 +27,7 @@ fn approve_dialog(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener
#[component]
pub fn cancel_dialog(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener) -> impl IntoView {
let cancel = create_server_action::<Cancel>();
let cancel = ServerAction::<Cancel>::new();
view! {
<QuestionDialog opener=opener action=cancel title="Cancel booking">
@@ -56,13 +56,13 @@ fn create_dialog(opener: DialogOpener) -> impl IntoView {
#[component]
pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView {
let create = DialogOpener::new();
let page = create_rw_signal(Page::new(5));
let first = create_rw_signal(false);
let last = create_rw_signal(false);
let total_pages = create_rw_signal(0i32);
let res = create_blocking_resource(move || (app_opener.visible() || cancel_opener.visible() || create.visible(), page),
let page = RwSignal::new(Page::new(5));
let first = RwSignal::new(false);
let last = RwSignal::new(false);
let total_pages = RwSignal::new(0i32);
let res = Resource::new_blocking(move || (app_opener.visible() || cancel_opener.visible() || create.visible(), page),
move |d| get_new_reservations(d.1.get()));
let reservation = create_rw_signal(ResSumWithItems::default());
let reservation = RwSignal::new(ResSumWithItems::default());
view! {
<ApproveDialog opener=app_opener reservation=reservation.read_only() />
@@ -75,7 +75,7 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
{move || {
res.get().map(|r| match r {
Err(e) => {
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}.into_any()}
Ok(r) => { match r {
ApiResponse::Data(r) => {
first.set(r.is_first());
@@ -98,7 +98,7 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
let:item>
{item.property.name}": "{item.reservation.from.to_string()}" - "{item.reservation.to.to_string()}<br/>
</For>
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email}</a>", "{data.customer.phone}<br/>
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email.clone()}</a>", "{data.customer.phone}<br/>
{move || {
let note = data.summary.note.clone();
let show = note.is_some() && !note.clone().unwrap().is_empty();
@@ -109,7 +109,7 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
}
}}
{trl("Price: ")}{data.summary.price.to_string()}<br/>
<table width="100%">
<table style="width: 100%"><tbody>
<tr>
<td width="100%"></td>
<td>
@@ -135,15 +135,15 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
</div>
</td>
</tr>
</table>
</tbody></table>
<hr/>
}
}}
</For>
</div>
}
}.into_any()
}
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}}}
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}.into_any()}}
}
})
}}
+11 -11
View File
@@ -1,5 +1,5 @@
use chrono::Weekday;
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{DayHours, WeekHours};
use crate::backend::opening_hours::get_hours;
use crate::components::modal_box::DialogOpener;
@@ -18,8 +18,8 @@ fn show_time(tm: &str) -> impl Fn() -> String {
pub fn OpeningHours() -> impl IntoView {
let editor = DialogOpener::new();
let closing_delete = DialogOpener::new();
let hours = create_blocking_resource(move || editor.visible(), move |_| {get_hours()});
let hrs = create_rw_signal(WeekHours::default());
let hours = Resource::new_blocking(move || editor.visible(), move |_| {get_hours()});
let hrs = RwSignal::new(WeekHours::default());
view! {
<EditHours opener=editor hours=hrs.read_only() />
@@ -27,16 +27,16 @@ pub fn OpeningHours() -> impl IntoView {
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title"><i class="bx bxs-watch"></i>" "{trl("Opening hours")}</h5>
<p class="card-text">
<div class="card-text">
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{move || {
hours.get().map(|h| match h {
Ok(h) => {
let h = create_rw_signal(h);
let d = create_rw_signal((0..7).collect::<Vec<u8>>());
let h = RwSignal::new(h);
let d = RwSignal::new((0..7).collect::<Vec<u8>>());
view! {
<div>
<table class="table card-table">
<table class="table card-table"><tbody>
<For each=move || d.get() key=|day| *day
children=move |day| {
let week_day = Weekday::try_from(day).unwrap_or(Weekday::Mon);
@@ -54,17 +54,17 @@ pub fn OpeningHours() -> impl IntoView {
}
}
/>
</table>
</tbody></table>
</div>
}
}.into_any()
}
Err(e) => {view! {<div><p>{trl("Error loading data")}</p>
<p>{e.to_string()}</p></div>
}}
}.into_any()}
})
}}
</Transition>
</p>
</div>
</div>
</div>
}
+3 -3
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::User;
use crate::backend::user::{get_user, UpdateProfile};
use crate::components::data_form::DataForm;
@@ -7,8 +7,8 @@ use crate::locales::trl;
#[component]
pub fn ProfileEdit(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
let update_user = create_server_action::<UpdateProfile>();
let logged_in = create_resource(||(), move |_| get_user());
let update_user = ServerAction::<UpdateProfile>::new();
let logged_in = Resource::new(||(), move |_| get_user());
view! {
<DataForm opener=opener action=update_user title="Edit profile">
+12 -12
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use leptos_use::use_media_query;
use crate::backend::data::{ApiResponse, ResProperty};
use crate::backend::property::get_properties;
@@ -11,13 +11,13 @@ use crate::pages::property_edit::PropertyEdit;
#[component]
pub fn properties() -> impl IntoView {
let is_wide = use_media_query("(min-width: 500px)");
let properties = create_rw_signal::<Vec<ResProperty>>(vec![]);
let prop = create_rw_signal(ResProperty::default());
let empty_prop = create_rw_signal(ResProperty::default());
let properties = RwSignal::<Vec<ResProperty>>::new(vec![]);
let prop = RwSignal::new(ResProperty::default());
let empty_prop = RwSignal::new(ResProperty::default());
let create_form = DialogOpener::new();
let edit_form = DialogOpener::new();
let delete_dlg = DialogOpener::new();
let props = create_blocking_resource(move || create_form.visible() || edit_form.visible() || delete_dlg.visible(), move |_| get_properties());
let props = Resource::new_blocking(move || create_form.visible() || edit_form.visible() || delete_dlg.visible(), move |_| get_properties());
view! {
<PropertyEdit property=prop.read_only() opener=edit_form edit=true/>
@@ -31,8 +31,8 @@ pub fn properties() -> impl IntoView {
<thead>
<tr>
<th>{trl("Name")}</th>
{move || if is_wide.get() {view! {<th>{trl("Description")}</th>}}
else {view! {<th></th>}} }
{move || if is_wide.get() {view! {<th>{trl("Description")}</th>}.into_any()}
else {view! {<th></th>}.into_any()} }
<th>{trl("Price")}</th>
<th>{trl("Actions")}</th>
</tr>
@@ -41,7 +41,7 @@ pub fn properties() -> impl IntoView {
props.get().map(|u| match u {
Err(e) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=4>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}}
<tr><td colspan=4>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}.into_any()}
Ok(u) => {
match u {
ApiResponse::Data(p) => {
@@ -58,8 +58,8 @@ pub fn properties() -> impl IntoView {
view! {
<tr>
<td>{data.name.clone()}</td>
{move || if is_wide.get() {view! {<td>{data.description.clone()}</td>}}
else {view! {<td></td>}} }
{move || if is_wide.get() {view! {<td>{data.description.clone()}</td>}.into_any()}
else {view! {<td></td>}.into_any()} }
<td>{data.price.to_string()}</td>
<td>
<div class="dropdown">
@@ -87,11 +87,11 @@ pub fn properties() -> impl IntoView {
}}
</For>
</tbody>
}
}.into_any()
},
ApiResponse::Error(s) => {
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=4>{trl(&s)}</td></tr></tbody>}
<tr><td colspan=4>{trl(&s)}</td></tr></tbody>}.into_any()
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::ResProperty;
use crate::backend::property::DeleteProperty;
use crate::components::data_form::QuestionDialog;
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
pub fn property_delete(property: ReadSignal<ResProperty>, opener: DialogOpener) -> impl IntoView {
let del_property = create_server_action::<DeleteProperty>();
let del_property = ServerAction::<DeleteProperty>::new();
view! {
<QuestionDialog opener=opener action=del_property title="Delete property">
+8 -8
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{ResProperty, SlotType};
use crate::backend::property::{CreateProperty, EditProperty};
use crate::components::data_form::DataForm;
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
fn form_inner(property: ReadSignal<ResProperty>) -> impl IntoView {
let active_str = create_rw_signal("false".to_string());
let active_str = RwSignal::new("false".to_string());
view! {
<input type="hidden" prop:value={move || property.get().id()} name="property[id]"/>
<div class="row">
@@ -79,9 +79,9 @@ fn form_inner(property: ReadSignal<ResProperty>) -> impl IntoView {
<input type="hidden" prop:value=active_str name="property[active]"/>
</div>
</div>
}
}.into_any()
} else {
view! {<div></div>}
view! {<div></div>}.into_any()
}
}
}}
@@ -89,19 +89,19 @@ fn form_inner(property: ReadSignal<ResProperty>) -> impl IntoView {
#[component]
pub fn property_edit(property: ReadSignal<ResProperty>, edit: bool, opener: DialogOpener) -> impl IntoView {
let action_create = create_server_action::<CreateProperty>();
let action_edit = create_server_action::<EditProperty>();
let action_create = ServerAction::<CreateProperty>::new();
let action_edit = ServerAction::<EditProperty>::new();
view! {
{move ||
if edit {
view! {<DataForm opener=opener action=action_edit title="Edit property">
<FormInner property=property/>
</DataForm>}}
</DataForm>}.into_any()}
else {
view! {<DataForm opener=opener action=action_create title="Create property">
<FormInner property=property/>
</DataForm>}}
</DataForm>}.into_any()}
}
}
}
+31 -29
View File
@@ -1,7 +1,8 @@
use chrono::{Duration, Local, NaiveDate, NaiveTime, Timelike};
use leptos::*;
use leptos::prelude::*;
use leptos_captcha::{Captcha, pow_dispatch};
use leptos_router::*;
use leptos::form::{ActionForm, FromFormData};
use leptos::ev::SubmitEvent;
use rust_decimal::Decimal;
use crate::backend::appearance::get_appearance;
use crate::backend::customer::get_remembered;
@@ -36,7 +37,7 @@ fn time_selector(
match property.slot {
SlotType::Quarter => {
let mut ret: Vec<TmCheck> = vec![];
logging::log!("quarter");
leptos::logging::log!("quarter");
for n in 0..(h.to() - h.from()).num_minutes() * 4 / 60 {
ret.push(TmCheck {
from: h.from() + Duration::minutes(n * 15),
@@ -47,7 +48,7 @@ fn time_selector(
}
SlotType::Half => {
let mut ret: Vec<TmCheck> = vec![];
logging::log!("half");
leptos::logging::log!("half");
for n in 0..(h.to() - h.from()).num_minutes() * 2 / 60 {
ret.push(TmCheck {
from: h.from() + Duration::minutes(n * 30),
@@ -110,22 +111,22 @@ fn time_selector(
#[component]
pub fn Public() -> impl IntoView {
let day = create_rw_signal(NaiveDate::default());
let form_data = create_blocking_resource(move || day.get(), move |d| get_public_form_data(d));
let slots = create_rw_signal::<Vec<String>>(vec![]);
let price = create_rw_signal(Decimal::from(0));
let cr_reservation = create_server_action::<CreateReservation>();
let day = RwSignal::new(NaiveDate::default());
let form_data = Resource::new_blocking(move || day.get(), move |d| get_public_form_data(d));
let slots = RwSignal::<Vec<String>>::new(vec![]);
let price = RwSignal::new(Decimal::from(0));
let cr_reservation = ServerAction::<CreateReservation>::new();
let validator = Validator::new();
let invalid_dlg = DialogOpener::new();
let result_dlg = DialogOpener::new();
let result = cr_reservation.value();
let is_pending = create_rw_signal(None);
let active_str = create_rw_signal("true".to_string());
let get_customer = create_blocking_resource(||(), move |_| get_remembered());
let appearance = create_blocking_resource(||(), move |_| get_appearance());
let customer = create_rw_signal(Customer::default());
let result = Signal::derive(move || cr_reservation.value().get());
let is_pending = RwSignal::new(None);
let active_str = RwSignal::new("true".to_string());
let get_customer = Resource::new_blocking(||(), move |_| get_remembered());
let appearance = Resource::new_blocking(||(), move |_| get_appearance());
let customer = RwSignal::new(Customer::default());
create_effect(move |_| {
Effect::new(move |_| {
day.set(Local::now().date_naive());
});
@@ -139,36 +140,37 @@ pub fn Public() -> impl IntoView {
captcha_state=is_pending.read_only()/>
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{
{move || {
appearance.get().map(|a| match a {
Ok(a) => {
let app = a.clone();
let has_banner = a.banner.is_some();
let title = a.title.clone().unwrap_or_default();
view! {
<div>
<Show when=move || a.clone().banner.is_some()>
<Show when=move || has_banner>
<div class="header_banner">
<h1 class="header_banner">{app.title.clone().unwrap_or_default()}</h1>
<h1 class="header_banner">{title.clone()}</h1>
</div>
</Show>
<div>
<div class="header_banner_text" inner_html={app.text.unwrap_or("".to_string())}>
<div class="header_banner_text" inner_html={a.text.unwrap_or("".to_string())}>
</div>
</div>
</div>
}
}.into_any()
},
Err(e) => {view! {<div><p>{trl("Error loading data")}</p>
<p>{e.to_string()}</p></div>
}}
}.into_any()}
})
}
}}
</Transition>
<div class="card-body">
<ActionForm
on:submit=move |ev| {
cr_reservation.set_pending(true);
// Capture phase, so the form is validated and the captcha solved before it is dispatched
on:submit:capture=move |ev: SubmitEvent| {
if let Ok(mut act) = CreateReservation::from_event(&ev) {
validator.check(act.entity(), &ev);
@@ -219,7 +221,7 @@ pub fn Public() -> impl IntoView {
});
form_data.get().map(|u| match u {
Err(e) => {
view! {<div>{e.to_string()}</div>}}
view! {<div>{e.to_string()}</div>}.into_any()}
Ok(u) => {
match u {
ApiResponse::Data(p) => {
@@ -252,10 +254,10 @@ pub fn Public() -> impl IntoView {
value={data}/>
</For>
</div>
}
}.into_any()
},
ApiResponse::Error(s) => {
view! {<div>{trl(&s)}</div>}
view! {<div>{trl(&s)}</div>}.into_any()
}
}}
})
+7 -6
View File
@@ -1,5 +1,5 @@
use chrono::{Local, NaiveDate};
use leptos::*;
use leptos::prelude::*;
use rust_decimal::Decimal;
use crate::backend::data::ApiResponse;
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog, ModalFooter};
@@ -29,7 +29,8 @@ pub fn res_error(opener: DialogOpener, validator: Validator) -> impl IntoView {
#[component]
pub fn res_saved(
opener: DialogOpener,
save_result: RwSignal<Option<Result<ApiResponse<NaiveDate>, ServerFnError>>>,
#[prop(into)]
save_result: Signal<Option<Result<ApiResponse<NaiveDate>, ServerFnError>>>,
day: RwSignal<NaiveDate>,
price: WriteSignal<Decimal>,
slots: WriteSignal<Vec<String>>,
@@ -68,7 +69,7 @@ pub fn res_saved(
</ModalFooter>
</ModalDialog>
</div>
}
}.into_any()
},
ApiResponse::Error(err) => {
view! {
@@ -88,7 +89,7 @@ pub fn res_saved(
</ModalFooter>
</ModalDialog>
</div>
}
}.into_any()
}
}
}
@@ -110,11 +111,11 @@ pub fn res_saved(
</ModalFooter>
</ModalDialog>
</div>
}
}.into_any()
}
}
} else {
view! {<div></div>}
view! {<div></div>}.into_any()
}
}}
}
+1 -1
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::locales::trl;
use crate::pages::closing_days::ClosingDays;
use crate::pages::company_info::CompanyInfo;
+9 -9
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{ApiResponse, ResSumWithItems};
use crate::backend::reservation::get_next_reservations;
use crate::components::modal_box::DialogOpener;
@@ -9,9 +9,9 @@ use crate::pages::new_reservations::CancelDialog;
#[component]
pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView {
let res = create_blocking_resource( move || app_opener.visible() || cancel_opener.visible(), move |_| get_next_reservations());
let res = Resource::new_blocking( move || app_opener.visible() || cancel_opener.visible(), move |_| get_next_reservations());
let menu = MenuOpener::new();
let reservation = create_rw_signal(ResSumWithItems::default());
let reservation = RwSignal::new(ResSumWithItems::default());
view! {
<CancelDialog opener=cancel_opener reservation=reservation.read_only()/>
@@ -22,7 +22,7 @@ pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener)
{move || {
res.get().map(|r| match r {
Err(e) => {
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}.into_any()}
Ok(r) => { match r {
ApiResponse::Data(r) => {
view! {
@@ -40,7 +40,7 @@ pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener)
let:item>
{item.property.name}": "{item.reservation.from.to_string()}" - "{item.reservation.to.to_string()}<br/>
</For>
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email}</a>", "{data.customer.phone}<br/>
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email.clone()}</a>", "{data.customer.phone}<br/>
{move || {
let note = data.summary.note.clone();
let show = note.is_some() && !note.clone().unwrap().is_empty();
@@ -51,7 +51,7 @@ pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener)
}
}}
{trl("Price: ")}{data.summary.price.to_string()}<br/>
<table width="100%">
<table style="width: 100%"><tbody>
<tr>
<td width="100%"></td>
<td>
@@ -72,15 +72,15 @@ pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener)
</div>
</td>
</tr>
</table>
</tbody></table>
<hr/>
}
}}
</For>
</div>
}
}.into_any()
}
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}}}
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}.into_any()}}
}
})
}}
+2 -2
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::User;
use crate::backend::user::DeleteUser;
use crate::components::data_form::QuestionDialog;
@@ -7,7 +7,7 @@ use crate::locales::trl;
#[component]
pub fn user_delete(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
let del_user = create_server_action::<DeleteUser>();
let del_user = ServerAction::<DeleteUser>::new();
view! {
<QuestionDialog opener=opener action=del_user title="Delete user">
+2 -2
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::user::CreateUser;
use crate::components::data_form::DataForm;
use crate::components::modal_box::DialogOpener;
@@ -6,7 +6,7 @@ use crate::locales::trl;
#[component]
pub fn user_edit(opener: DialogOpener) -> impl IntoView {
let create_usr = create_server_action::<CreateUser>();
let create_usr = ServerAction::<CreateUser>::new();
view! {
<DataForm opener=opener action=create_usr title="Create user">
//<input type="hidden" value={move || company.get().id()} name="company[id]"/>
+14 -14
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use crate::backend::data::{ApiResponse, User};
use crate::backend::user::get_users;
use crate::components::modal_box::DialogOpener;
@@ -16,10 +16,10 @@ pub fn users() -> impl IntoView {
let profile_editor = DialogOpener::new();
let pwd_dialog = DialogOpener::new();
let delete_dialog = DialogOpener::new();
let users = create_blocking_resource(
let users = Resource::new_blocking(
move || editor.visible() || profile_editor.visible() || delete_dialog.visible(), move |_| {get_users()});
let (usr, set_usr) = create_signal::<Vec<User>>(vec![]);
let (profile, set_profile) = create_signal(User::default());
let (usr, set_usr) = signal::<Vec<User>>(vec![]);
let (profile, set_profile) = signal(User::default());
let is_wide = use_media_query("(min-width: 500px)");
view! {
@@ -35,8 +35,8 @@ pub fn users() -> impl IntoView {
<thead>
<tr>
<th>{trl("Username")}</th>
{move || if is_wide.get() {view! {<th>{trl("Full name")}</th>}}
else {view! {<th></th>}} }
{move || if is_wide.get() {view! {<th>{trl("Full name")}</th>}.into_any()}
else {view! {<th></th>}.into_any()} }
<th>{trl("Admin")}</th>
<th>{trl("Actions")}</th>
</tr>
@@ -50,7 +50,7 @@ pub fn users() -> impl IntoView {
e.to_string()
};
view! {<tbody class="table-border-bottom-0">
<tr><td colspan=4>{trl(&err)}</td></tr></tbody>}}
<tr><td colspan=4>{trl(&err)}</td></tr></tbody>}.into_any()}
Ok(u) => {
match u {
ApiResponse::Data(u) => {
@@ -66,11 +66,11 @@ pub fn users() -> impl IntoView {
let full_name = user.full_name.clone().unwrap_or("".to_string());
view! {
<tr>
<td>{&user.login}</td>
{move || if is_wide.get() {view! {<td>{full_name.clone()}</td>}}
else {view! {<td></td>}} }
<td>{if user.admin {view! {<i class="bx bx-check"></i>}}
else {view! {<i></i>}}}</td>
<td>{user.login.clone()}</td>
{move || if is_wide.get() {view! {<td>{full_name.clone()}</td>}.into_any()}
else {view! {<td></td>}.into_any()} }
<td>{if user.admin {view! {<i class="bx bx-check"></i>}.into_any()}
else {view! {<i></i>}.into_any()}}</td>
<td>
<div class="dropdown">
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
@@ -101,10 +101,10 @@ pub fn users() -> impl IntoView {
</tr>
}
}/></tbody>
}
}.into_any()
}
ApiResponse::Error(e) => {view! {<tbody class="table-border-bottom-0">
<tr><td colspan=4>{trl(&e)}</td></tr></tbody>}}
<tr><td colspan=4>{trl(&e)}</td></tr></tbody>}.into_any()}
}
}
})
+4 -4
View File
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::prelude::*;
use validator::Validate;
#[derive(Copy, Clone)]
@@ -13,9 +13,9 @@ pub struct Validator {
impl Validator {
pub fn new() -> Self {
let (valid, set_valid) = create_signal(true);
//let (message, set_message) = create_signal(None);
let (messages, set_messages) = create_signal(None);
let (valid, set_valid) = signal(true);
//let (message, set_message) = signal(None);
let (messages, set_messages) = signal(None);
Self {
//message,
//set_message,