Cargo fmt holy

This commit is contained in:
Franklin 2023-04-23 11:11:38 -04:00
parent 6b20ac66f1
commit fc2a2510ec
43 changed files with 940 additions and 376 deletions

View File

@ -32,6 +32,5 @@ err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" }
thousands = "0.2.0"
chrono = "0.4.23"
# Core
jl-types = { path = "../jl-types", features = ["wasm"] }

View File

@ -0,0 +1,77 @@
.mediapicker-container {
display: flex;
flex-direction: row;
flex-flow: row wrap;
justify-content: start;
align-items: center;
gap: 10px;
width: 100%;
}
.mediapicker-media {
display: flex;
object-fit: cover;
width: 240px;
height: 240px;
}
.mediapicker-new {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
width: 240px;
height: 240px;
font-size: 40px;
background-color: gainsboro;
color: white;
}
.mediapicker-new:hover {
cursor: pointer;
}
.mediapicker-delete {
position: relative;
right: 25px;
top: 10px;
color: grey;
font-size: 18px;
width: fit-content;
height: fit-content;
}
.mediapicker-delete:hover {
cursor: pointer;
}
.mediapicker-new-photo-section {
width: 90%;
height: 30px;
font-size: 13px;
font-family: Source Sans Pro;
background-color: white;
color: black;
padding: 3px;
display: flex;
justify-content: center;
align-items: center;
}
.mediapicker-new-video-section {
width: 90%;
height: 30px;
background-color: white;
color: black;
padding: 3px;
display: flex;
justify-content: center;
align-items: center;
}
.mediapicker-new-video-section-input {
height: 100%;
background-color: white;
border: solid 0.5px #d8d8d8;
width: 100%;
text-indent: 10px;
font-family: Source Sans Pro;
}

View File

@ -30,6 +30,7 @@
<link data-trunk type="text/css" href="css/components/admin_nav_bar.css" rel="css" />
<link data-trunk type="text/css" href="css/components/admin_project.css" rel="css" />
<link data-trunk type="text/css" href="css/components/datepicker.css" rel="css" />
<link data-trunk type="text/css" href="css/components/mediapicker.css" rel="css" />
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">

View File

@ -1,6 +1,12 @@
use std::collections::HashSet;
use jl_types::{dto::{filters::Filter, listing::Listing, project_card::ProjectCardDto, payloads::contact::ContactPayload}, domain::{agent::Agent, count::Count, contact::Contact}};
use jl_types::{
domain::{agent::Agent, contact::Contact, count::Count},
dto::{
filters::Filter, listing::Listing, payloads::contact::ContactPayload,
project_card::ProjectCardDto,
},
};
use reqwest::Method;
use uuid::Uuid;
@ -9,38 +15,140 @@ use super::base::perform_request_without_client;
const BASE_URL: &str = "http://localhost:8095/";
pub async fn get_all_cities() -> Result<HashSet<String>, err::Error> {
perform_request_without_client::<String, HashSet<String>>(BASE_URL.into(), Method::GET, "read/locations".into(), None, 200, Vec::new(), None).await
perform_request_without_client::<String, HashSet<String>>(
BASE_URL.into(),
Method::GET,
"read/locations".into(),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_districts_in_city(city: &String) -> Result<HashSet<String>, err::Error> {
perform_request_without_client::<String, HashSet<String>>(BASE_URL.into(), Method::GET, format!("read/locations/{city}"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, HashSet<String>>(
BASE_URL.into(),
Method::GET,
format!("read/locations/{city}"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_projects_with_filters_paged(page: &i64, filters: Vec<Filter>) -> Result<Vec<ProjectCardDto>, err::Error> {
perform_request_without_client::<String, Vec<ProjectCardDto>>(BASE_URL.into(), Method::GET, format!("read/projects/{page}"), None, 200, Vec::new(), Some(filters.into_iter().map(|filter| filter.to_param()).collect())).await
pub async fn get_all_projects_with_filters_paged(
page: &i64,
filters: Vec<Filter>,
) -> Result<Vec<ProjectCardDto>, err::Error> {
perform_request_without_client::<String, Vec<ProjectCardDto>>(
BASE_URL.into(),
Method::GET,
format!("read/projects/{page}"),
None,
200,
Vec::new(),
Some(
filters
.into_iter()
.map(|filter| filter.to_param())
.collect(),
),
)
.await
}
pub async fn get_project_listing(project_id: &Uuid) -> Result<Listing, err::Error> {
perform_request_without_client::<String, Listing>(BASE_URL.into(), Method::GET, format!("read/project/{project_id}"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Listing>(
BASE_URL.into(),
Method::GET,
format!("read/project/{project_id}"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_agents() -> Result<Vec<Agent>, err::Error> {
perform_request_without_client::<String, Vec<Agent>>(BASE_URL.into(), Method::GET, format!("read/agent"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Vec<Agent>>(
BASE_URL.into(),
Method::GET,
format!("read/agent"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_agent_with_shortcode(shortcode: &String) -> Result<Agent, err::Error> {
perform_request_without_client::<String, Agent>(BASE_URL.into(), Method::GET, format!("read/agent/{shortcode}"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Agent>(
BASE_URL.into(),
Method::GET,
format!("read/agent/{shortcode}"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_page_visits_count() -> Result<Count, err::Error> {
perform_request_without_client::<String, Count>(BASE_URL.into(), Method::GET, format!("admin/visits/count"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Count>(
BASE_URL.into(),
Method::GET,
format!("admin/visits/count"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_contacts_count() -> Result<Count, err::Error> {
perform_request_without_client::<String, Count>(BASE_URL.into(), Method::GET, format!("admin/contacts/count"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Count>(
BASE_URL.into(),
Method::GET,
format!("admin/contacts/count"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn get_all_contacts() -> Result<Vec<Contact>, err::Error> {
perform_request_without_client::<String, Vec<Contact>>(BASE_URL.into(), Method::GET, format!("admin/contacts"), None, 200, Vec::new(), None).await
perform_request_without_client::<String, Vec<Contact>>(
BASE_URL.into(),
Method::GET,
format!("admin/contacts"),
None,
200,
Vec::new(),
None,
)
.await
}
pub async fn create_new_contact_request(contact: ContactPayload) -> Result<(), err::Error> {
perform_request_without_client(BASE_URL.into(), Method::POST, format!("read/contact"), Some(contact), 200, Vec::new(), None).await
}
perform_request_without_client(
BASE_URL.into(),
Method::POST,
format!("read/contact"),
Some(contact),
200,
Vec::new(),
None,
)
.await
}
/*
pub async fn upload_image() -> Result<String, err::Error> {
perform_request_without_client(BASE_URL.into(), Method::POST, format!("images/"), Some(), 200, Vec::new(), None).await
}*/

View File

@ -1,7 +1,6 @@
use err::{Error, MessageResource};
use reqwest::Client;
use serde::{Serialize, de::DeserializeOwned};
use serde::{de::DeserializeOwned, Serialize};
/// This function is mainly for when you don't have a client in your application and just want to get it over with.
/// This shouldn't be used as it takes more resource consumption than the above method.
@ -12,7 +11,7 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
body: Option<B>,
expected_status_code: u16,
headers: Vec<(String, String)>,
params: Option<Vec<(String, String)>>
params: Option<Vec<(String, String)>>,
) -> Result<R, Error> {
let client = Client::new();
let mut req_incomplete =
@ -25,7 +24,7 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
if let Some(parameters) = params {
req_incomplete = req_incomplete.query(&parameters)
}
let req_complete = match body {
Some(b) => req_incomplete.json(&b),
None => req_incomplete.header("content-length", 0),
@ -60,4 +59,4 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
Err(Error::Network(MessageResource::from(e)))
}
}
}
}

View File

@ -1,2 +1,2 @@
pub mod backend;
pub mod base;
pub mod backend;

View File

@ -1,10 +1,9 @@
use yew::prelude::*;
use yew_router::prelude::{use_navigator, use_route};
use crate::{routes::main_router::Route};
use crate::routes::main_router::Route;
//use yew_router::prelude::use_navigator;
#[function_component(AdminNavigationBar)]
pub fn admin_navigation_bar() -> Html {
let current_route: Option<Route> = use_route();
@ -13,7 +12,7 @@ pub fn admin_navigation_bar() -> Html {
let cloned_navigator_2 = navigator.clone();
let cloned_navigator_3 = navigator.clone();
let cloned_navigator_4 = navigator.clone();
html! {
<div class={"admin-navbar-background"}>
<div class={"admin-navbar-image"}>
@ -27,8 +26,8 @@ pub fn admin_navigation_bar() -> Html {
<div class={ if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::AdminContacts) {"admin-navbar-element-selected"} else {"admin-navbar-element"} } onclick={move |_| cloned_navigator_4.push(&Route::AdminContacts)}>{"Mensajes"}</div>
<div class={"admin-navbar-divider"}></div>
</div>
</div>
}
}
}

View File

@ -1,8 +1,11 @@
use jl_types::{dto::project_card::ProjectCardDto, domain::project_state::ProjectState};
use jl_types::{domain::project_state::ProjectState, dto::project_card::ProjectCardDto};
use yew::prelude::*;
use yew_router::{prelude::use_navigator};
use yew_router::prelude::use_navigator;
use crate::{routes::main_router::Route, pages::admin::edit::{EditType, EditItem}};
use crate::{
pages::admin::edit::{EditItem, EditType},
routes::main_router::Route,
};
#[function_component(AdminProject)]
pub fn admin_project(props: &AdminProjectProps) -> Html {
@ -24,7 +27,10 @@ pub fn admin_project(props: &AdminProjectProps) -> Html {
let onclick_item = {
let props = props.clone();
Callback::from(move |_| {
navigator.push(&Route::AdminEdit { edit_type: EditType::Existing(props.project.id), edit_item: EditItem::Project });
navigator.push(&Route::AdminEdit {
edit_type: EditType::Existing(props.project.id),
edit_item: EditItem::Project,
});
})
};
html! {
@ -59,4 +65,4 @@ pub fn admin_project(props: &AdminProjectProps) -> Html {
pub struct AdminProjectProps {
pub project: ProjectCardDto,
pub index: usize,
}
}

View File

@ -30,4 +30,4 @@ pub fn agent_card(props: &AgentCardProps) -> Html {
#[derive(Properties, PartialEq)]
pub struct AgentCardProps {
pub agent: Agent,
}
}

View File

@ -1,25 +1,25 @@
use std::str::FromStr;
use chrono::{NaiveDate};
use chrono::NaiveDate;
use yew::prelude::*;
use crate::components::textfield::get_value_from_input_event;
/// The input type date field in html will always guarantee a valid date string.
/// The input type date field in html will always guarantee a valid date string.
#[function_component(DatePicker)]
pub fn datepicker(props: &DatePickerProps) -> Html {
let date_handle = props.value.clone();
let optional_cb = props.onchange.clone();
let cb = Callback::from(move |e: InputEvent| {
match parse_date(get_value_from_input_event(e)) {
Ok(date) => {
Ok(date) => {
match optional_cb.clone() {
Some(callback) => callback.emit(Some(date.clone())),
None => {},
None => {}
};
date_handle.set(Some(date));
},
Err(_) => {},
}
Err(_) => {}
};
});
let date_handle = props.value.clone();
@ -40,7 +40,7 @@ pub struct DatePickerProps {
pub value: UseStateHandle<Option<NaiveDate>>,
#[prop_or_default]
pub required: bool,
pub onchange: Option<Callback<Option<NaiveDate>>>
pub onchange: Option<Callback<Option<NaiveDate>>>,
}
pub fn parse_date(date_str: String) -> Result<NaiveDate, ()> {
@ -49,6 +49,6 @@ pub fn parse_date(date_str: String) -> Result<NaiveDate, ()> {
Err(error) => {
log::error!("Falied to parse Date in DatePicker: {error}");
Err(())
},
}
}
}
}

View File

@ -1,12 +1,14 @@
use std::fmt::Display;
use jl_types::domain::{option_wrapper::OptionWrapper};
use jl_types::domain::option_wrapper::OptionWrapper;
use yew::prelude::*;
use yew_utils::vdom::comp_with;
/// Give this component a list of options
#[function_component(DropDown)]
pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(props: &DropDownProps<T>) -> Html {
pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(
props: &DropDownProps<T>,
) -> Html {
let selection_changed_cb = {
let onchange_cb = props.onchange.clone();
let selected = props.selected.clone();
@ -14,22 +16,29 @@ pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(props: &Drop
selected.set(option.option.clone());
match onchange_cb.clone() {
Some(additional_cb) => additional_cb.emit(option),
None => {},
None => {}
};
})
};
let drop_down = comp_with::<yew_utils::components::drop_down::DropDown<OptionWrapper<T>>>(yew_utils::components::drop_down::DropDownProps {
initial: {
OptionWrapper::new((*props.selected).clone())},
options: {
let mut options: Vec<OptionWrapper<T>> = props.options.clone().into_iter().map(|option| OptionWrapper::new(Some(option))).collect();
if props.has_none { options.insert(0, OptionWrapper::new(None)); }
options
let drop_down = comp_with::<yew_utils::components::drop_down::DropDown<OptionWrapper<T>>>(
yew_utils::components::drop_down::DropDownProps {
initial: { OptionWrapper::new((*props.selected).clone()) },
options: {
let mut options: Vec<OptionWrapper<T>> = props
.options
.clone()
.into_iter()
.map(|option| OptionWrapper::new(Some(option)))
.collect();
if props.has_none {
options.insert(0, OptionWrapper::new(None));
}
options
},
selection_changed: selection_changed_cb.clone(),
class_css: Some("admin-dropdown".into()),
},
selection_changed: selection_changed_cb.clone(),
class_css: Some("admin-dropdown".into())
});
);
//if (*props.selected).is_none() {selection_changed_cb.emit( OptionWrapper::new((*props.selected).clone()));}
html! {
{drop_down}
@ -47,5 +56,5 @@ pub struct DropDownProps<T: Display + std::cmp::PartialEq + Clone> {
#[prop_or_default]
pub onchange: Option<Callback<OptionWrapper<T>>>,
#[prop_or_default]
pub has_none: bool
}
pub has_none: bool,
}

View File

@ -3,11 +3,11 @@ use yew::prelude::*;
#[function_component(FeatureItem)]
pub fn feature_item(props: &FeatureItemProps) -> Html {
let props = props.clone();
html!{
html! {
<div class={"details-body-feature-item"}>
<div class={"details-body-feature-item-icon-container"}>
<i class={props.icon}></i>
</div>
</div>
<div class={"details-body-feature-item-text-container"}>
<div class={"details-body-feature-item-text-title"}>
{props.title}
@ -27,4 +27,4 @@ pub struct FeatureItemProps {
pub title: String,
pub subtitle: String,
pub icon: String,
}
}

View File

@ -1,7 +1,6 @@
use uuid::Uuid;
use yew::prelude::*;
//TODO: Finish, add whatsapp link or email link?
#[function_component(FloatingWidget)]
pub fn floating_widget(props: &FloatingWidgetProps) -> Html {
let message = format!("Buenas, me interesa conocer mas sobre esta propiedad: %0A https://proyectosenconstruccion.com/details/{}", props.project_id);
@ -20,4 +19,4 @@ pub fn floating_widget(props: &FloatingWidgetProps) -> Html {
pub struct FloatingWidgetProps {
pub project_id: Uuid,
pub phone_number: String,
}
}

View File

@ -27,9 +27,9 @@ pub fn page_footer() -> Html {
</div>
</div>
/*
<div class={"footer-call-to-action-container"}>
@ -39,7 +39,7 @@ pub fn page_footer() -> Html {
</div>
</div>
*/
</div>
}
}
}

View File

@ -0,0 +1,129 @@
use jl_types::domain::media::{Media, MediaList};
use yew::prelude::*;
use crate::components::textfield::get_value_from_input_event;
#[function_component(MediaPicker)]
pub fn media_picker(props: &MediaPickerProps) -> Html {
html! {
<>
<div class={{"textfield-label-required"}}>{"Media del proyecto"}</div>
<div class={"mediapicker-container"}>
<MediaListRendered medialist={props.value.clone()} onchange={props.onchange.clone()}/>
</div>
</>
}
}
#[derive(PartialEq, Properties, Clone)]
pub struct MediaPickerProps {
pub value: UseStateHandle<MediaList>,
pub onchange: Option<Callback<String>>,
#[prop_or_default]
pub required: bool,
}
#[function_component(MediaListRendered)]
fn render_media_list(props: &MediaListProps) -> Html {
let mut media_elements = (*props.medialist).clone().media_list.into_iter().enumerate().map(|(index, media)| {
html! {
<div class={"mediapicker-media"}>
{
match media {
Media::Photo(url) => html! {
<img class={"mediapicker-media"} src={url.clone()} />
},
Media::Video(url) => html! {
<iframe class={"mediapicker-media"} src={format!("{url}?&mute=1")}></iframe>
}
}
}
<div class={"mediapicker-delete"} onclick={
let media_handle = props.medialist.clone();
let onchange_cb = props.onchange.clone();
Callback::from(move |_| {
match onchange_cb.clone() {
Some(cb) => cb.emit(String::new()),
None => {}
};
let mut media = (*media_handle).clone();
media.media_list.remove(index);
media_handle.set(media);
})
}>
<i class="fa-solid fa-trash-can"></i>
</div>
</div>
}
}).collect::<Vec<Html>>();
let pressed = use_state(|| false);
let onclick = {
let pressed = pressed.clone();
Callback::from(move |event: MouseEvent| {
event.stop_propagation();
pressed.set(true);
})
};
let video_str = use_state_eq(|| String::new());
let onvideo_input_changed = {
let video_str = video_str.clone();
Callback::from(move |e: InputEvent| {
let value = get_value_from_input_event(e);
video_str.set(value);
})
};
let onvideo_keypress = {
let video_str = video_str.clone();
let media_handle = props.medialist.clone();
let onchange_cb = props.onchange.clone();
Callback::from(move |e: KeyboardEvent| {
if e.key() == String::from("Enter") {
match onchange_cb.clone() {
Some(cb) => cb.emit(String::new()),
None => {}
};
let mut media = (*media_handle).clone();
media.media_list.push(Media::Video((*video_str).clone()));
media_handle.set(media);
}
})
};
let onphoto_upload = {
let media_handle = props.medialist.clone();
let onchange_cb = props.onchange.clone();
Callback::from(move |_: InputEvent| {
match onchange_cb.clone() {
Some(cb) => cb.emit(String::new()),
None => {}
};
//TODO: Upload picture, then push url into medialist
let mut media = (*media_handle).clone();
media.media_list.push(Media::Photo("".into()));
media_handle.set(media);
})
};
media_elements.push(html! {
<div class={"mediapicker-new"} onclick={onclick}>
{if *pressed {
html! {
<>
<div class={"mediapicker-new-photo-section"}>
<input type={"file"} accept={"image/*"} oninput={onphoto_upload}/>
</div>
<div class={"mediapicker-new-video-section"}>
<input class={"mediapicker-new-video-section-input"} oninput={onvideo_input_changed} onkeypress={onvideo_keypress} placeholder={"Video de Youtube"}/>
</div>
</>
}
} else {
html! {<i class="fa-solid fa-plus"></i>}
}}
</div>
});
media_elements.into_iter().collect()
}
#[derive(Properties, PartialEq)]
pub struct MediaListProps {
pub medialist: UseStateHandle<MediaList>,
pub onchange: Option<Callback<String>>,
}

View File

@ -1,7 +1,6 @@
use jl_types::domain::media::Media;
use yew::prelude::*;
#[function_component(MediaSlideshow)]
pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
let current_media_index = use_state(|| 0);
@ -11,7 +10,7 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
let total_media_count = total_media_count.clone();
Callback::from(move |_| {
if *current_media_index > 0 {
current_media_index.set(*current_media_index -1);
current_media_index.set(*current_media_index - 1);
} else {
current_media_index.set(total_media_count - 1);
}
@ -47,7 +46,7 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
"display: none;"
}}
src={format!("{url}?autoplay=1&mute=1")}>
</iframe>
</iframe>
}
}
}).collect::<Html>()
@ -69,4 +68,4 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
#[derive(Properties, PartialEq, PartialOrd)]
pub struct MediaSlideshowProps {
pub media_list: Vec<Media>,
}
}

View File

@ -1,12 +1,14 @@
pub mod nav_bar;
pub mod project_card;
pub mod media_slideshow;
pub mod floating_widget;
pub mod feature;
pub mod footer;
pub mod agent_card;
pub mod admin_nav_bar;
pub mod admin_project;
pub mod textfield;
pub mod agent_card;
pub mod datepicker;
pub mod dropdown;
pub mod datepicker;
pub mod feature;
pub mod floating_widget;
pub mod footer;
pub mod media_picker;
pub mod media_slideshow;
pub mod nav_bar;
pub mod new_widget;
pub mod project_card;
pub mod textfield;

View File

@ -1,12 +1,15 @@
use stdweb::web::{IEventTarget, event::ResizeEvent};
use stdweb::web::{event::ResizeEvent, IEventTarget};
use yew::prelude::*;
use yew_router::prelude::{use_navigator, use_route};
use crate::{routes::main_router::{Route}, constants::{NAVBAR_COL_LANDING, NAVBAR_COL_CONTACTO, NAVBAR_COL_PROYECTOS, NAVBAR_COL_AGENTES}};
use crate::{
constants::{
NAVBAR_COL_AGENTES, NAVBAR_COL_CONTACTO, NAVBAR_COL_LANDING, NAVBAR_COL_PROYECTOS,
},
routes::main_router::Route,
};
//use yew_router::prelude::use_navigator;
#[function_component(NavigationBar)]
pub fn navigation_bar() -> Html {
let current_route: Option<Route> = use_route();
@ -23,10 +26,12 @@ pub fn navigation_bar() -> Html {
let navbar_toggle = navbar_toggle.clone();
Callback::from(move |_| navbar_toggle.set(!*navbar_toggle))
};
let window_device_handle = use_state_eq(|| if stdweb::web::window().inner_width() > 750 {
WindowDevice::Desktop
} else {
WindowDevice::Mobile
let window_device_handle = use_state_eq(|| {
if stdweb::web::window().inner_width() > 750 {
WindowDevice::Desktop
} else {
WindowDevice::Mobile
}
});
let window_device_handle_cloned = window_device_handle.clone();
stdweb::web::window().add_event_listener(move |_: ResizeEvent| {
@ -37,16 +42,15 @@ pub fn navigation_bar() -> Html {
window_device_handle_cloned.set(WindowDevice::Mobile)
}
});
html! {
<div class={"navbar-background"}>
<div class={"navbar-container"}>
<div class={"navbar-brand-container"} onclick={move |_| cloned_navigator_5.push(&Route::LandingPage)}>
<img class={"navbar-image"} src={ if (*window_device_handle) == WindowDevice::Mobile {"images/logo-white.png"} else {"images/logo-color.png"}} alt=""/>
</div>
<div class={if *navbar_toggle {"navbar-closed"} else {"navbar-open"}}>
<div onclick={move |_| cloned_navigator_1.push(&Route::LandingPage)} class={
if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::LandingPage) {"navbar-item-selected"} else {"navbar-item"}
@ -109,7 +113,7 @@ pub fn navigation_bar() -> Html {
}
}
}
</div>
}
}
@ -117,5 +121,5 @@ pub fn navigation_bar() -> Html {
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum WindowDevice {
Desktop,
Mobile
}
Mobile,
}

View File

@ -0,0 +1,34 @@
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{
pages::admin::edit::{EditItem, EditType},
routes::main_router::Route,
};
#[function_component(NewThingWidget)]
pub fn new_thing_widget(props: &NewThingWidgetProps) -> Html {
let navigator = use_navigator().unwrap();
let onclick = {
let edit_item = props.item.clone();
let navigator = navigator.clone();
Callback::from(move |_| {
navigator.push(&Route::AdminEdit {
edit_type: EditType::New,
edit_item: edit_item.clone(),
});
})
};
html! {
<a class={"floating-widget-container"} style={"background-color: #02114A;"} onclick={onclick}>
<div class={"floating-widget-logo"}>
<i class="fa-solid fa-plus"></i>
</div>
</a>
}
}
#[derive(Properties, PartialEq)]
pub struct NewThingWidgetProps {
pub item: EditItem,
}

View File

@ -1,16 +1,15 @@
use jl_types::{domain::{media::Media}, dto::project_card::ProjectCardDto};
use jl_types::{domain::media::Media, dto::project_card::ProjectCardDto};
use thousands::Separable;
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::routes::main_router::Route;
#[function_component(ProjectCard)]
pub fn project_card(props: &ProjectCardProps) -> Html {
let navigator = use_navigator().unwrap();
let project_id = props.project.id.clone();
let project_view_cb = Callback::from(move |_|{
let project_view_cb = Callback::from(move |_| {
navigator.push(&Route::Details { project_id });
});
@ -25,30 +24,35 @@ pub fn project_card(props: &ProjectCardProps) -> Html {
cover_image_url = String::new()
}
let project_title = format!("{} en {}, {}", props.project.project_type, props.project.district, props.project.city);
let project_price = format!("Desde US${}", match props.project.starts_from {
Some(price) => {
let price_separated = price.separate_with_commas();
if price_separated.contains(".") {
price_separated
} else {
format!("{price_separated}.00")
let project_title = format!(
"{} en {}, {}",
props.project.project_type, props.project.district, props.project.city
);
let project_price = format!(
"Desde US${}",
match props.project.starts_from {
Some(price) => {
let price_separated = price.separate_with_commas();
if price_separated.contains(".") {
price_separated
} else {
format!("{price_separated}.00")
}
}
},
None => "0.00".into()
});
None => "0.00".into(),
}
);
let project_condition = props.project.project_condition.to_string();
let project_est_finish_date = props.project.finish_date.format("%m/%Y");
//let project_location_written;
html!{
html! {
<div class={"project-listing-card"} onclick={project_view_cb}>
<img src={cover_image_url} alt={"project image"} class={"project-search-result-card-picture"}/>
<div class={"project-search-result-card-title"}>
{project_title}
</div>
@ -71,5 +75,5 @@ pub fn project_card(props: &ProjectCardProps) -> Html {
#[derive(Properties, PartialEq, PartialOrd)]
pub struct ProjectCardProps {
pub project: ProjectCardDto
}
pub project: ProjectCardDto,
}

View File

@ -3,7 +3,6 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::{HtmlInputElement, HtmlTextAreaElement};
use yew::prelude::*;
/// This component is a text
#[function_component(TextField)]
pub fn textfield(props: &TextFieldProps) -> Html {
@ -11,24 +10,26 @@ pub fn textfield(props: &TextFieldProps) -> Html {
let handle = props.value.clone();
let fieldtype = props.fieldtype.clone();
let onchange = props.onchange.clone();
Callback::from(move |e: InputEvent| {
match fieldtype {
TextFieldType::Input => {
let value = get_value_from_input_event(e);
match onchange.clone() {
Some(onchange) => { onchange.emit(value.clone()); },
None => {}
};
handle.set(value);
},
TextFieldType::TextArea => {
let value = get_value_from_input_event(e);
match onchange.clone() {
Some(onchange) => { onchange.emit(value.clone()); },
None => {}
};
handle.set(value);
},
Callback::from(move |e: InputEvent| match fieldtype {
TextFieldType::Input => {
let value = get_value_from_input_event(e);
match onchange.clone() {
Some(onchange) => {
onchange.emit(value.clone());
}
None => {}
};
handle.set(value);
}
TextFieldType::TextArea => {
let value = get_value_from_textarea_event(e);
match onchange.clone() {
Some(onchange) => {
onchange.emit(value.clone());
}
None => {}
};
handle.set(value);
}
})
};
@ -42,7 +43,7 @@ pub fn textfield(props: &TextFieldProps) -> Html {
html! { <textarea class={"textarea"} oninput={on_input_changed} value={(*props.value).clone()}/> }
}
}
</div>
}
}
@ -55,7 +56,7 @@ pub struct TextFieldProps {
pub fieldtype: TextFieldType,
#[prop_or_default]
pub required: bool,
pub onchange: Option<Callback<String>>
pub onchange: Option<Callback<String>>,
}
#[derive(PartialEq, Clone, Default)]
@ -77,4 +78,4 @@ pub fn get_value_from_textarea_event(e: InputEvent) -> String {
let event_target = event.target().unwrap_throw();
let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw();
target.value()
}
}

View File

@ -4,4 +4,4 @@ pub const NAVBAR_COL_CONTACTO: &str = "Vende con nosotros";
pub const NAVBAR_COL_AGENTES: &str = "Agentes";
pub const PRIMARY_COLOR: &str = "#41BDD9";
pub const SECONDARY_COLOR: &str = "#5D6A73";
pub const SECONDARY_COLOR: &str = "#5D6A73";

View File

@ -1,10 +1,9 @@
use yew::prelude::*;
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_agents};
use crate::{api::backend::get_all_agents, components::admin_nav_bar::AdminNavigationBar};
#[function_component(AdminAgents)]
pub fn admin_agents() -> Html {
let agents = use_state(|| Vec::new());
use_state(|| {
@ -13,7 +12,7 @@ pub fn admin_agents() -> Html {
match get_all_agents().await {
Ok(agents) => {
agents_handle.set(agents);
},
}
Err(error) => log::error!("Error retrieving agents from backend. Error: {}", error),
};
});
@ -29,4 +28,4 @@ pub fn admin_agents() -> Html {
</div>
</>
}
}
}

View File

@ -1,6 +1,6 @@
use yew::prelude::*;
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_contacts};
use crate::{api::backend::get_all_contacts, components::admin_nav_bar::AdminNavigationBar};
#[function_component(AdminContacts)]
pub fn admin_contacts() -> Html {
@ -12,8 +12,10 @@ pub fn admin_contacts() -> Html {
match get_all_contacts().await {
Ok(contacts) => {
contacts_handle.set(contacts);
},
Err(error) => log::error!("Error retrieving contacts from backend. Error: {}", error),
}
Err(error) => {
log::error!("Error retrieving contacts from backend. Error: {}", error)
}
};
});
});
@ -23,9 +25,9 @@ pub fn admin_contacts() -> Html {
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
<div class={"admin-panel-page-title"}>{"Solicitudes de Contacto"}</div>
</div>
</div>
</>
}
}
}

View File

@ -1,11 +1,26 @@
use std::{fmt::Display, str::FromStr};
use jl_types::{ domain::{agent::Agent, media::{MediaList}, unit::Unit, project_state::ProjectState, project_condition::ProjectCondition, project_type::ProjectType}, dto::listing::Listing};
use jl_types::{
domain::{
agent::Agent, media::MediaList, project_condition::ProjectCondition,
project_state::ProjectState, project_type::ProjectType, unit::Unit,
},
dto::listing::Listing,
};
use uuid::Uuid;
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{components::{admin_nav_bar::AdminNavigationBar, textfield::{TextField, TextFieldType}, dropdown::DropDown, datepicker::DatePicker}, api::backend::{get_project_listing, get_all_agents}};
use crate::{
api::backend::{get_all_agents, get_project_listing},
components::{
admin_nav_bar::AdminNavigationBar,
datepicker::DatePicker,
dropdown::DropDown,
media_picker::MediaPicker,
textfield::{TextField, TextFieldType},
},
};
/// All of the editing actions of the admin panel will lead to here. This should take an id of anything. A unit, a project, an agent. And its corresponding ID.
#[function_component(AdminEditPage)]
@ -15,34 +30,38 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
use_state(|| {
let listing = listing.clone();
match props.edit_item {
EditItem::Agent => {
},
EditItem::Agent => {}
EditItem::Project => {
match props.edit_type {
EditType::New => {},
EditType::New => {}
EditType::Existing(uid) => {
wasm_bindgen_futures::spawn_local(async move {
let listing_result = get_project_listing(&uid).await;
match listing_result {
Ok(listing_persisted) => {
listing.set(Some(listing_persisted));
},
Err(error) => log::error!("Error loading listing: {error}")
}
Err(error) => log::error!("Error loading listing: {error}"),
};
});
},
}
};
},
EditItem::Unit(_) => {},
}});
}
EditItem::Unit(_) => {}
}
});
html! {
<>
<AdminNavigationBar/>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
<div class={"admin-panel-page-title"}>{ format!("Editar {}", match props.edit_item {
<div class={"admin-panel-page-title"}>{ format!("{} {}",
match props.edit_type {
EditType::New => "Agregar",
EditType::Existing(_) => "Editar",
},
match props.edit_item {
EditItem::Agent => "Agente",
EditItem::Project => "Proyecto",
EditItem::Unit(_) => "Unidad",
@ -59,21 +78,21 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
</div>
</div>
</>
}
}
#[derive(PartialEq, Clone)]
pub enum EditItem {
Agent,
Project,
Project,
/// Project Uid
Unit(Uuid),
}
#[derive(PartialEq, Clone)]
pub enum EditType {
New, Existing(Uuid)
New,
Existing(Uuid),
}
#[derive(Properties, PartialEq)]
@ -92,7 +111,7 @@ pub struct ProjectFieldsProps {
pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
let user_typed = use_state(|| false);
let listing_opt = props.listing.clone();
let location_city = use_state_eq(|| String::new());
let location_district = use_state_eq(|| String::new());
let agent: UseStateHandle<Option<Agent>> = use_state_eq(|| None);
@ -103,7 +122,9 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
let project_admin_tag: UseStateHandle<String> = use_state_eq(|| String::new());
let project_finish_date = use_state_eq(|| None);
let project_floors = use_state_eq(|| String::new());
let media: UseStateHandle<MediaList> = use_state_eq(|| MediaList { media_list: Vec::new() });
let media: UseStateHandle<MediaList> = use_state_eq(|| MediaList {
media_list: Vec::new(),
});
let units: UseStateHandle<Vec<Unit>> = use_state_eq(|| Vec::new());
let ontype_cb = {
@ -112,6 +133,12 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
user_typed.set(true);
})
};
let ontypedate_cb = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
user_typed.set(true);
})
};
let onselect_cb = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
@ -144,12 +171,14 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
wasm_bindgen_futures::spawn_local(async move {
match get_all_agents().await {
Ok(persisted_agents) => all_agents.set(persisted_agents),
Err(error) => log::error!("Error fetching agents from admin panel edit screen: {error}"),
Err(error) => {
log::error!("Error fetching agents from admin panel edit screen: {error}")
}
};
})
});
if !*user_typed {
if !*user_typed {
location_city.set(listing_opt.clone().unwrap_or_default().location.city);
location_district.set(listing_opt.clone().unwrap_or_default().location.district);
agent.set(match listing_opt.clone() {
@ -169,12 +198,12 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
None => None,
});
project_description.set(match listing_opt.clone() {
Some(listing) => listing.project.description,
None => "".into(),
Some(listing) => listing.project.description,
None => "".into(),
});
project_floors.set(match listing_opt.clone() {
Some(listing) => listing.project.floors.to_string(),
None => "".into(),
Some(listing) => listing.project.floors.to_string(),
None => "".into(),
});
project_admin_tag.set(match listing_opt.clone() {
Some(listing) => match listing.project.admin_tag {
@ -187,13 +216,27 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
Some(listing) => Some(listing.project.finish_date.date()),
None => None,
});
media.set(match listing_opt.clone() {
Some(listing) => listing.project.media,
None => MediaList {
media_list: Vec::new(),
},
});
}
html! {
<>
<TextField label={"Ciudad"} value={location_city} required={true} onchange={ontype_cb.clone()}/>
<TextField label={"Distrito"} value={location_district} required={true} onchange={ontype_cb.clone()} />
{if (*agent).clone().is_none() { html! { } } else {
<MediaPicker value={media} onchange={ontype_cb.clone()}/>
{if (*agent).clone().is_none() {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Agente asignado"}</div>
<DropDown<Agent> options={(*all_agents).clone()} selected={agent} onchange={onselect_cb.clone()} />
</div>
}
} else {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Agente asignado"}</div>
@ -202,7 +245,14 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
}
}
}
{if (*project_state).clone().is_none() { html! { } } else {
{if (*project_state).clone().is_none() {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Estado del Proyecto"}</div>
<DropDown<ProjectState> options={vec![ProjectState::Finished, ProjectState::InConstruction]} selected={project_state} onchange={onselect_cb2} />
</div>
}
} else {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Estado del Proyecto"}</div>
@ -211,9 +261,16 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
}
}
}
{if (*project_condition).clone().is_none() { html! { } } else {
html! {
{if (*project_condition).clone().is_none() {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Condición del Proyecto"}</div>
<DropDown<ProjectCondition> options={vec![ProjectCondition::Resale, ProjectCondition::New]} selected={project_condition} onchange={onselect_cb3} />
</div>
}
} else {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Condición del Proyecto"}</div>
<DropDown<ProjectCondition> options={vec![ProjectCondition::Resale, ProjectCondition::New]} selected={project_condition} onchange={onselect_cb3} />
@ -221,8 +278,15 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
}
}
}
{if (*project_type).clone().is_none() { html! { } } else {
html! {
{if (*project_type).clone().is_none() {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Tipo de Proyecto"}</div>
<DropDown<ProjectType> options={vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]} selected={project_type} onchange={onselect_cb4} />
</div>
}
} else {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Tipo de Proyecto"}</div>
<DropDown<ProjectType> options={vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]} selected={project_type} onchange={onselect_cb4} />
@ -230,10 +294,10 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
}
}
}
<TextField label={"Pisos"} value={project_floors} required={true} />
<DatePicker label={"Fecha de entrega Est."} value={project_finish_date} required={true} />
<TextField label={"Descripción"} value={project_description} fieldtype={TextFieldType::TextArea} />
<TextField label={"Comentario interno"} value={project_admin_tag} />
<TextField label={"Pisos"} value={project_floors} required={true} onchange={ontype_cb.clone()}/>
<DatePicker label={"Fecha de entrega Est."} value={project_finish_date} required={true} onchange={ontypedate_cb.clone()}/>
<TextField label={"Descripción"} value={project_description} fieldtype={TextFieldType::TextArea} onchange={ontype_cb.clone()}/>
<TextField label={"Comentario interno"} value={project_admin_tag} onchange={ontype_cb.clone()}/>
<div class={"admin-edit-submit-button"}>
{"Actualizar"}
@ -258,7 +322,7 @@ impl Display for EditType {
EditType::Existing(id) => {
let fmt_id = id.to_string().replace("-", "");
write!(f, "existing{fmt_id}")
},
}
}
}
}
@ -276,7 +340,7 @@ impl FromStr for EditItem {
match s {
"agent" => Ok(Self::Agent),
"project" => Ok(Self::Project),
_ => Err(())
_ => Err(()),
}
}
}
@ -295,7 +359,7 @@ impl FromStr for EditType {
}
match s {
"new" => Ok(Self::New),
_ => Err(())
_ => Err(()),
}
}
}
}

View File

@ -1,7 +1,7 @@
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{utils::input::get_value_from_input_event, routes::main_router::Route};
use crate::{routes::main_router::Route, utils::input::get_value_from_input_event};
#[function_component(AdminLoginPage)]
pub fn login_page() -> Html {
@ -25,7 +25,7 @@ pub fn login_page() -> Html {
};
let onclick = {
let error = error.clone();
Callback::from(move |_| {
let error = error.clone();
let navigator = navigator.clone();
@ -59,6 +59,6 @@ pub fn login_page() -> Html {
</div>
</div>
</div>
}
}
}

View File

@ -1,6 +1,6 @@
pub mod login;
pub mod start;
pub mod projects;
pub mod contacts;
pub mod agents;
pub mod edit;
pub mod contacts;
pub mod edit;
pub mod login;
pub mod projects;
pub mod start;

View File

@ -1,10 +1,15 @@
use yew::prelude::*;
use crate::{components::{admin_nav_bar::AdminNavigationBar, admin_project::AdminProject}, api::backend::get_all_projects_with_filters_paged};
use crate::{
api::backend::get_all_projects_with_filters_paged,
components::{
admin_nav_bar::AdminNavigationBar, admin_project::AdminProject, new_widget::NewThingWidget,
},
pages::admin::edit::EditItem,
};
#[function_component(AdminProjects)]
pub fn admin_projects() -> Html {
let projects = use_state(|| Vec::new());
use_state(|| {
@ -13,25 +18,26 @@ pub fn admin_projects() -> Html {
match get_all_projects_with_filters_paged(&1, Vec::new()).await {
Ok(projects) => {
projects_handle.set(projects);
},
Err(error) => log::error!("Error retrieving projects from backend. Error: {}", error),
}
Err(error) => {
log::error!("Error retrieving projects from backend. Error: {}", error)
}
};
});
});
html! {
<>
<AdminNavigationBar/>
<NewThingWidget item={EditItem::Project}/>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
<div class={"admin-panel-page-title"}>{"Proyectos"}</div>
<div class={"admin-projects-table"}>
<div class={"admin-navbar-divider"}></div>
{(*projects).clone().into_iter().enumerate().map(|(key, project)| html! {
{(*projects).clone().into_iter().enumerate().map(|(key, project)| html! {
<>
<AdminProject project={project} index={key}/>
<AdminProject project={project} index={key}/>
<div class={"admin-navbar-divider"}></div>
</>
}).collect::<Html>()}
@ -40,4 +46,4 @@ pub fn admin_projects() -> Html {
</div>
</>
}
}
}

View File

@ -27,4 +27,4 @@ pub fn admin_start() -> Html {
</div>
</>
}
}
}

View File

@ -1,8 +1,10 @@
use jl_types::domain::agent::Agent;
use yew::prelude::*;
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter, agent_card::AgentCard}, api::backend::get_all_agents};
use crate::{
api::backend::get_all_agents,
components::{agent_card::AgentCard, footer::PageFooter, nav_bar::NavigationBar},
};
#[function_component(AgentsPage)]
pub fn agents_page() -> Html {
@ -14,16 +16,18 @@ pub fn agents_page() -> Html {
wasm_bindgen_futures::spawn_local(async move {
match get_all_agents().await {
Ok(mut fetched_agents) => {
if let Some(jl_agent_pos) = fetched_agents.iter().position(|agent| agent.full_name == "Jorge Ledesma") {
if let Some(jl_agent_pos) = fetched_agents
.iter()
.position(|agent| agent.full_name == "Jorge Ledesma")
{
let removed_agent = fetched_agents.remove(jl_agent_pos);
fetched_agents.insert(0, removed_agent);
}
agents.set(fetched_agents);
finished_loading.set(true);
},
Err(error) => log::error!("Error fetching listing. Error: {:?}", error)
};
}
Err(error) => log::error!("Error fetching listing. Error: {:?}", error),
};
});
});
html! {
@ -52,4 +56,4 @@ pub fn agents_page() -> Html {
<PageFooter/>
</>
}
}
}

View File

@ -2,12 +2,15 @@ use jl_types::dto::payloads::contact::ContactPayload;
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter}, utils::input::{get_value_from_input_event, get_value_from_textarea_event}, api::backend::create_new_contact_request, routes::main_router::Route};
use crate::{
api::backend::create_new_contact_request,
components::{footer::PageFooter, nav_bar::NavigationBar},
routes::main_router::Route,
utils::input::{get_value_from_input_event, get_value_from_textarea_event},
};
#[function_component(ContactPage)]
pub fn contact_page() -> Html {
let navigator = use_navigator().unwrap();
let first_name = use_state(|| String::new());
@ -50,17 +53,23 @@ pub fn contact_page() -> Html {
first_name: (*first_name).clone(),
last_name: (*last_name).clone(),
credential: (*credential).clone(),
message: (*messsage).clone()
message: (*messsage).clone(),
};
let navigator = navigator.clone();
if contact.first_name.len() > 1 && contact.last_name.len() > 1 && contact.credential.len() > 3 && contact.message.len() > 5 {
if contact.first_name.len() > 1
&& contact.last_name.len() > 1
&& contact.credential.len() > 3
&& contact.message.len() > 5
{
wasm_bindgen_futures::spawn_local(async move {
match create_new_contact_request(contact).await {
Ok(_) => {
// TODO: Take to homepage? Say thanks?
navigator.push(&Route::LandingPage);
},
Err(error) => log::error!("Error in sending a contact request to the backend: {error}")
}
Err(error) => log::error!(
"Error in sending a contact request to the backend: {error}"
),
};
});
}
@ -86,7 +95,7 @@ pub fn contact_page() -> Html {
<div class={"contact-us-textfield-label"}>{"Nombre"}</div>
<input class={"contact-us-textfield"} oninput={on_fn_input_changed}/>
</div>
<div class={"contact-us-textfield-container"}> // Last name
<div class={"contact-us-textfield-label"}>{"Apellido"}</div>
<input class={"contact-us-textfield"} oninput={on_ln_input_changed}/>
@ -111,4 +120,4 @@ pub fn contact_page() -> Html {
<PageFooter/>
</>
}
}
}

View File

@ -1,11 +1,21 @@
use jl_types::{dto::listing::Listing, domain::{unit_type::UnitType, unit::Unit, agent::Agent}};
use log::{error};
use jl_types::{
domain::{agent::Agent, unit::Unit, unit_type::UnitType},
dto::listing::Listing,
};
use log::error;
use thousands::Separable;
use uuid::Uuid;
use yew::prelude::*;
use yew_router::{prelude::{use_location}};
use yew_router::prelude::use_location;
use crate::{components::{nav_bar::NavigationBar, media_slideshow::MediaSlideshow, floating_widget::FloatingWidget, feature::FeatureItem, footer::PageFooter}, api::backend::{get_project_listing, get_agent_with_shortcode}, utils::storage::{self, StorageKey}};
use crate::{
api::backend::{get_agent_with_shortcode, get_project_listing},
components::{
feature::FeatureItem, floating_widget::FloatingWidget, footer::PageFooter,
media_slideshow::MediaSlideshow, nav_bar::NavigationBar,
},
utils::storage::{self, StorageKey},
};
const ALPHABET_LETTERS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@ -21,8 +31,10 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
if query_param.0 == String::from("shortcode") {
// Store shortcode in localstorage
match storage::store_in_local_storage(StorageKey::AgentShortcode, &query_param.1) {
Ok(_) => {},
Err(error) => log::error!("Error storing agent shortcode in localstorage\n: {}", error),
Ok(_) => {}
Err(error) => {
log::error!("Error storing agent shortcode in localstorage\n: {}", error)
}
};
}
}
@ -36,15 +48,20 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
let override_agent_handle = override_agent_handle.clone();
wasm_bindgen_futures::spawn_local(async move {
match get_agent_with_shortcode(&persisted_shortcode).await {
Ok(agent) => { override_agent_handle.set(Some(agent)) },
Err(error) => error!("Error fetching agent with shortcode. Error: {:?}", error),
Ok(agent) => override_agent_handle.set(Some(agent)),
Err(error) => {
error!("Error fetching agent with shortcode. Error: {:?}", error)
}
};
})
},
}
None => {}
}
},
Err(error) => log::error!("Error reading agent shortcode from localstorage\n: {}", error),
}
Err(error) => log::error!(
"Error reading agent shortcode from localstorage\n: {}",
error
),
};
// Project loading part
let finished_loading = use_state(|| false);
@ -59,39 +76,56 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
Ok(listing) => {
listing_handle.set(listing);
finished_loading.set(true);
},
}
Err(error) => {
error!("Error fetching listing. Error: {:?}", error);
},
}
};
});
});
let listing = (*listing_handle).clone();
let mut forsale_units: Vec<&Unit> = listing.units.iter().filter(|unit| unit.unit_type == UnitType::ForSale).collect();
let mut not_forsale_units: Vec<&Unit> = listing.units.iter().filter(|unit| unit.unit_type == UnitType::NotForSale).collect();
let mut forsale_units: Vec<&Unit> = listing
.units
.iter()
.filter(|unit| unit.unit_type == UnitType::ForSale)
.collect();
let mut not_forsale_units: Vec<&Unit> = listing
.units
.iter()
.filter(|unit| unit.unit_type == UnitType::NotForSale)
.collect();
let mut organized_units = Vec::new();
organized_units.append(&mut forsale_units);
organized_units.append(&mut not_forsale_units);
let project_title = format!("{} en {}, {}", &listing.project.project_type, &listing.location.district, &listing.location.city);
let project_title = format!(
"{} en {}, {}",
&listing.project.project_type, &listing.location.district, &listing.location.city
);
//let project_description = &listing.project.description;
let project_media_list = listing.project.media.media_list;
let project_type = listing.project.project_type.to_string();
let project_condition = listing.project.project_condition.to_string();
let project_est_finish_date = format!("{} {}", listing.project.finish_date.format("%m/%Y"), if listing.project.finish_date.timestamp_millis() <= chrono::Utc::now().timestamp_millis() {
"(Listo)"
} else {""});
let project_est_finish_date = format!(
"{} {}",
listing.project.finish_date.format("%m/%Y"),
if listing.project.finish_date.timestamp_millis() <= chrono::Utc::now().timestamp_millis() {
"(Listo)"
} else {
""
}
);
let project_floors = listing.project.floors.to_string();
let cloned_selected_unit_handle = selected_unit_handle.clone();
let selected_unit_opt = organized_units.get(*cloned_selected_unit_handle);
html!{
html! {
<>
<NavigationBar/>
<FloatingWidget phone_number={
<FloatingWidget phone_number={
if let Some(overriden_agent) = (*override_agent_handle).clone()
{ overriden_agent.credential.clone() } else { listing.agent.credential.clone() }
} project_id={listing.project.id}/>
@ -103,7 +137,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
<div class={"details-head"}>
<div class={"details-head-title"}>{project_title}</div>
<MediaSlideshow media_list={project_media_list}/>
// Agent
<div class={"details-head-agent-container"}>
// Profile pic
@ -111,7 +145,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
if let Some(overriden_agent) = (*override_agent_handle).clone()
{ overriden_agent.profile_picture_url.clone() } else { listing.agent.profile_picture_url.clone() }
} alt={"agent_pfp"}/>
<div class={"details-head-agent-text-container"}>
<div class={"details-head-agent-heading"}>{
if let Some(overriden_agent) = (*override_agent_handle).clone()
@ -125,11 +159,11 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
}</div>
</div>
</div>
<div class={"details-body-divider"}></div>
</div>
<div class={"details-body"}>
<div class={"details-body-description"}>
@ -150,10 +184,10 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
<FeatureItem title={"Tipo de Proyecto"} icon={"fa-solid fa-house-chimney"} subtitle={project_type}/>
<FeatureItem title={"Cant. de Pisos"} icon={"fa-solid fa-building"} subtitle={project_floors}/>
</div>
<div class={"details-body-divider"}></div>
//
//
// Units part
//
@ -163,7 +197,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! {
<div class={"details-body-units"}>
<div class={"details-body-units-title"}>{"Unidades"}</div>
{create_unit_selection_menu(&organized_units, selected_unit_handle)}
{
@ -193,8 +227,8 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! {}
}
}
<div class={"details-body-units-media-slideshow-frame"}>
{
@ -209,7 +243,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
}
}
</div>
<div class={"details-body-units-description"}>
{
if unit.description.len() == 0 {
@ -225,11 +259,11 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
{"Aplica para financiamiento"}
</a>
</div>
<div class={"details-body-divider"}></div>
<div class={"details-body-disclaimer"}>
{"*Unidades sujetas a disponibilidad"}
@ -247,7 +281,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
}
}
}
</div>
</div>
}
@ -259,8 +293,8 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
}
}
}
</div>
{
if *finished_loading {
@ -269,14 +303,14 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! {}
}
}
</>
}
}
#[derive(Properties, PartialEq)]
pub struct DetailsPageProps {
pub project_id: Uuid
pub project_id: Uuid,
}
pub fn format_phone_number<'a>(phone_number: &'a String) -> String {
@ -289,8 +323,10 @@ pub fn format_phone_number<'a>(phone_number: &'a String) -> String {
}
}
pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseStateHandle<usize>) -> Html {
pub fn create_unit_selection_menu(
units: &Vec<&Unit>,
selected_unit_handle: UseStateHandle<usize>,
) -> Html {
html! {
<div class={"details-body-units-selection-container"}>
{ // Maps all the units to generate the unit selection menu
@ -302,9 +338,9 @@ pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseS
};
html! {
<div class={
if *cloned_selected_unit_handle == index
{"details-body-units-selection-item-selected"}
else
if *cloned_selected_unit_handle == index
{"details-body-units-selection-item-selected"}
else
{"details-body-units-selection-item-unselected"}
} onclick={select_unit_onclick_cb}>
{
@ -323,13 +359,13 @@ pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseS
}
}
}
</div>
}
}).collect::<Html>()
}
</div>
}
}
}

View File

@ -1,20 +1,21 @@
use yew::prelude::*;
use yew_router::prelude::{use_navigator};
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter}, routes::main_router::Route};
use yew_router::prelude::use_navigator;
use crate::{
components::{footer::PageFooter, nav_bar::NavigationBar},
routes::main_router::Route,
};
#[function_component(LandingPage)]
pub fn landing_page() -> Html {
let navigator = use_navigator().unwrap();
let go_to_proyect_page_on_click = Callback::from(move |_| navigator.push(&Route::Search));
html!{
html! {
<>
<NavigationBar/>
<div class={"page-container"} style={"margin-bottom: 10vh"}> //TODO: remove this margin when landing page done
// Main section
<div class={"lp-main-cover-slide"}>
<div class={"lp-main-cover-slide-text-container"}>
@ -27,7 +28,7 @@ pub fn landing_page() -> Html {
<div class={"lp-main-cover-slide-art-detail"}/>
</div>
</div>
// Second section
<div class={"lp-second-cover-slide-container"}>
<div class={"lp-second-cover-slide"}>
@ -74,12 +75,12 @@ pub fn landing_page() -> Html {
<div class={"lp-testimonials-section-card-subtitle"}>{"Santo Domingo"}</div>
<div class={"lp-testimonials-section-card-description"}>{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fames ut dictumst urna, lorem nibh. Pretium leo hendrerit interdum netus"}</div>
</div>
</div>
</div>
</div>
<PageFooter/>
</>
}
}
}

View File

@ -1,7 +1,7 @@
pub mod landing;
pub mod search;
pub mod details;
pub mod not_found;
pub mod contact;
pub mod admin;
pub mod agents;
pub mod agents;
pub mod contact;
pub mod details;
pub mod landing;
pub mod not_found;
pub mod search;

View File

@ -1,6 +1,6 @@
use yew::prelude::*;
use crate::components::{nav_bar::NavigationBar, footer::PageFooter};
use crate::components::{footer::PageFooter, nav_bar::NavigationBar};
#[function_component(NotFoundPage)]
pub fn not_found_page() -> Html {
@ -13,4 +13,4 @@ pub fn not_found_page() -> Html {
<PageFooter/>
</>
}
}
}

View File

@ -1,11 +1,23 @@
use jl_types::{domain::{project_state::ProjectState, project_type::ProjectType, project_condition::ProjectCondition}, dto::{filters::Filter, project_card::ProjectCardDto}};
use jl_types::{
domain::{
project_condition::ProjectCondition, project_state::ProjectState, project_type::ProjectType,
},
dto::{filters::Filter, project_card::ProjectCardDto},
};
use log::info;
use yew::prelude::*;
use yew_utils::{components::drop_down::{DropDownProps, DropDown}, vdom::comp_with};
use yew_utils::{
components::drop_down::{DropDown, DropDownProps},
vdom::comp_with,
};
use crate::{
api::backend::{
get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged,
},
components::{footer::PageFooter, nav_bar::NavigationBar, project_card::ProjectCard},
};
use jl_types::domain::option_wrapper::OptionWrapper;
use crate::{components::{nav_bar::NavigationBar, project_card::ProjectCard, footer::PageFooter}, api::backend::{get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged}};
#[function_component(SearchPage)]
pub fn search_page() -> Html {
@ -16,7 +28,7 @@ pub fn search_page() -> Html {
let page_counter: UseStateHandle<i64> = use_state(|| 1);
let finished_loading = use_state(|| false);
let project_state_filter_handle = use_state_eq(|| ProjectState::InConstruction);
// All code to execute on first render and never again
use_state(|| {
let cities_handle = cities_handle.clone();
@ -26,51 +38,70 @@ pub fn search_page() -> Html {
wasm_bindgen_futures::spawn_local(async move {
match get_all_cities().await {
Ok(cities) => {
let mut cities: Vec<OptionWrapper<String>> = cities.into_iter().map(|location| OptionWrapper::new(Some(location))).collect();
let mut cities: Vec<OptionWrapper<String>> = cities
.into_iter()
.map(|location| OptionWrapper::new(Some(location)))
.collect();
cities.insert(0, OptionWrapper::new(None));
cities_handle.set(cities);
},
Err(error) => log::error!("Error in loading cities: {error}")
}
Err(error) => log::error!("Error in loading cities: {error}"),
};
match get_all_projects_with_filters_paged(&(*page_counter), vec![Filter::InConstruction]).await {
match get_all_projects_with_filters_paged(
&(*page_counter),
vec![Filter::InConstruction],
)
.await
{
Ok(projects) => {
search_results_handle.set(projects);
finished_loading.set(true);
},
}
Err(error) => log::error!("Error in loading projects: {error}"),
};
});
});
// Dropdown
let project_type_filter: UseStateHandle<OptionWrapper<ProjectType>> = use_state(|| OptionWrapper::new(None));
let project_type_filter: UseStateHandle<OptionWrapper<ProjectType>> =
use_state(|| OptionWrapper::new(None));
// Dropdown
let project_condition_filter: UseStateHandle<OptionWrapper<ProjectCondition>> = use_state(|| OptionWrapper::new(None));
let project_condition_filter: UseStateHandle<OptionWrapper<ProjectCondition>> =
use_state(|| OptionWrapper::new(None));
// Dropdown
// let project_state_filter: UseStateHandle<OptionWrapper<ProjectState>> = use_state(|| OptionWrapper::new(Some(props.project_state.clone())));
// Dropdown
let project_city_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
let project_city_filter: UseStateHandle<OptionWrapper<String>> =
use_state(|| OptionWrapper::new(None));
// Dropdown
let project_district_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
let unit_rooms_filter: UseStateHandle<OptionWrapper<usize>> = use_state(|| OptionWrapper::new(None));
let project_district_filter: UseStateHandle<OptionWrapper<String>> =
use_state(|| OptionWrapper::new(None));
let unit_rooms_filter: UseStateHandle<OptionWrapper<usize>> =
use_state(|| OptionWrapper::new(None));
//TODO: Think about price filtering
/*// TextField
let _project_min_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));
// TextField
let _project_max_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));*/
let project_type_drop_down = comp_with::<DropDown<OptionWrapper<ProjectType>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectType::Apartamento)), OptionWrapper::new(Some(ProjectType::Casa)), OptionWrapper::new(Some(ProjectType::Oficina)), OptionWrapper::new(Some(ProjectType::Local)), OptionWrapper::new(Some(ProjectType::Solar)) ],
options: vec![
OptionWrapper::new(None),
OptionWrapper::new(Some(ProjectType::Apartamento)),
OptionWrapper::new(Some(ProjectType::Casa)),
OptionWrapper::new(Some(ProjectType::Oficina)),
OptionWrapper::new(Some(ProjectType::Local)),
OptionWrapper::new(Some(ProjectType::Solar)),
],
selection_changed: {
let cloned_project_type_filter = project_type_filter.clone();
Callback::from(move |project_type: OptionWrapper<ProjectType>| {
info!("{}", project_type.to_string());
cloned_project_type_filter.set(project_type)
}
)},
class_css: Some("project-search-filter-item".into())
})
},
class_css: Some("project-search-filter-item".into()),
});
/*
@ -87,19 +118,24 @@ pub fn search_page() -> Html {
)},
class_css: Some("project-search-filter-item".into())
});*/
let project_condition_drop_down = comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectCondition::New)), OptionWrapper::new(Some(ProjectCondition::Resale)) ],
selection_changed: {
let cloned_project_condition_filter = project_condition_filter.clone();
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| {
info!("{}", project_condition.to_string());
cloned_project_condition_filter.set(project_condition)
}
)},
class_css: Some("project-search-filter-item".into())
});
let project_condition_drop_down =
comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![
OptionWrapper::new(None),
OptionWrapper::new(Some(ProjectCondition::New)),
OptionWrapper::new(Some(ProjectCondition::Resale)),
],
selection_changed: {
let cloned_project_condition_filter = project_condition_filter.clone();
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| {
info!("{}", project_condition.to_string());
cloned_project_condition_filter.set(project_condition)
})
},
class_css: Some("project-search-filter-item".into()),
});
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
initial: OptionWrapper::new(None),
@ -114,16 +150,19 @@ pub fn search_page() -> Html {
wasm_bindgen_futures::spawn_local(async move {
match get_all_districts_in_city(&project_city.to_string()).await {
Ok(districts) => {
let mut districts_vec: Vec<OptionWrapper<String>> = districts.into_iter().map(|district| OptionWrapper::new(Some(district))).collect();
let mut districts_vec: Vec<OptionWrapper<String>> = districts
.into_iter()
.map(|district| OptionWrapper::new(Some(district)))
.collect();
districts_vec.insert(0, OptionWrapper::new(None));
districts_handle.set(districts_vec);
},
}
Err(error) => log::error!("Error in dropdown callback: {}", error),
};
});
}
)},
class_css: Some("project-search-filter-item".into())
})
},
class_css: Some("project-search-filter-item".into()),
});
let project_district_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
@ -133,21 +172,33 @@ pub fn search_page() -> Html {
let cloned_project_district_filter = project_district_filter.clone();
Callback::from(move |project_district: OptionWrapper<String>| {
cloned_project_district_filter.set(project_district)
}
)},
class_css: Some("project-search-filter-item".into())
})
},
class_css: Some("project-search-filter-item".into()),
});
let unit_room_amount_drop_down = comp_with::<DropDown<OptionWrapper<usize>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(1)), OptionWrapper::new(Some(2)), OptionWrapper::new(Some(3)), OptionWrapper::new(Some(4)), OptionWrapper::new(Some(5)), OptionWrapper::new(Some(6)), OptionWrapper::new(Some(7)), OptionWrapper::new(Some(8)), OptionWrapper::new(Some(9)), OptionWrapper::new(Some(10))],
options: vec![
OptionWrapper::new(None),
OptionWrapper::new(Some(1)),
OptionWrapper::new(Some(2)),
OptionWrapper::new(Some(3)),
OptionWrapper::new(Some(4)),
OptionWrapper::new(Some(5)),
OptionWrapper::new(Some(6)),
OptionWrapper::new(Some(7)),
OptionWrapper::new(Some(8)),
OptionWrapper::new(Some(9)),
OptionWrapper::new(Some(10)),
],
selection_changed: {
let unit_rooms_filter = unit_rooms_filter.clone();
Callback::from(move |unit_room_amount: OptionWrapper<usize>| {
unit_rooms_filter.set(unit_room_amount)
})
},
class_css: Some("project-search-filter-item".into())
class_css: Some("project-search-filter-item".into()),
});
let search_onclick = {
@ -168,51 +219,50 @@ pub fn search_page() -> Html {
}
match &(*project_type_filter).option {
Some(project_type) => filters.push(Filter::ByProjectType(project_type.clone())),
None => {},
None => {}
};
match &(*project_condition_filter).option {
Some(project_condition) => filters.push(Filter::ByProjectCondition(project_condition.clone())),
None => {},
Some(project_condition) => {
filters.push(Filter::ByProjectCondition(project_condition.clone()))
}
None => {}
};
match &(*project_city_filter).option {
Some(project_city) => filters.push(Filter::InCity(project_city.clone())),
None => {},
None => {}
};
match &(*project_district_filter).option {
Some(project_district) => filters.push(Filter::InDistrict(project_district.clone())),
None => {},
Some(project_district) => {
filters.push(Filter::InDistrict(project_district.clone()))
}
None => {}
};
match &(*unit_rooms_filter).option {
Some(rooms_filter) => filters.push(Filter::ByRoomCount(*rooms_filter as i32)),
None => {},
None => {}
};
let search_results_handle = search_results_handle.clone();
let page_counter = page_counter.clone();
wasm_bindgen_futures::spawn_local(async move {
match get_all_projects_with_filters_paged(&(*page_counter), filters).await {
Ok(projects) => {
search_results_handle.set(projects)
},
Ok(projects) => search_results_handle.set(projects),
Err(error) => log::error!("Error in loading projects: {error}"),
};
});
})};
})
};
let inconstruction_state_select_onclick = {
let project_state_filter = project_state_filter_handle.clone();
Callback::from(move |_| {
project_state_filter.set(ProjectState::InConstruction)
})
Callback::from(move |_| project_state_filter.set(ProjectState::InConstruction))
};
let finished_state_select_onclick = {
let project_state_filter = project_state_filter_handle.clone();
Callback::from(move |_| {
project_state_filter.set(ProjectState::Finished)
})
Callback::from(move |_| project_state_filter.set(ProjectState::Finished))
};
html!{
html! {
<>
<NavigationBar/>
<div class={"page-container"}>
@ -223,7 +273,7 @@ pub fn search_page() -> Html {
{"Estado del Proyecto"}
</div>
<div class={"project-search-filter-select"}>
<div class={if (*project_state_filter_handle) == ProjectState::InConstruction {
"project-search-filter-select-item-selected"
} else {
@ -261,7 +311,7 @@ pub fn search_page() -> Html {
</div>
{project_condition_drop_down}
</div>
<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Ciudad"}
@ -295,10 +345,10 @@ pub fn search_page() -> Html {
html!{
<div class={"project-search-results-container"}> // Search Results Content
{if (*search_results_handle).len() == 0 {
html!{
html!{
<div style={"margin-bottom: 40vh"}>
{"No se han encontrado proyectos con los filtros de busqueda especificados."}
</div>
</div>
}
} else {
html! {}
@ -311,11 +361,11 @@ pub fn search_page() -> Html {
<div class={"project-search-results-container"}> // Search Results Content
<div class="lds-facebook"><div></div><div></div><div></div></div>
</div>
}
}
}
</div>
{
if *finished_loading {

View File

@ -1,9 +1,24 @@
use yew_router::prelude::*;
use yew::prelude::*;
use yew_router::prelude::*;
use uuid::Uuid;
use crate::{pages::{landing::LandingPage, search::{SearchPage}, details::DetailsPage, not_found::NotFoundPage, contact::ContactPage, admin::{login::AdminLoginPage, start::AdminStart, projects::AdminProjects, agents::AdminAgents, contacts::AdminContacts, edit::{EditType, EditItem, AdminEditPage}}, agents::AgentsPage}};
use crate::pages::{
admin::{
agents::AdminAgents,
contacts::AdminContacts,
edit::{AdminEditPage, EditItem, EditType},
login::AdminLoginPage,
projects::AdminProjects,
start::AdminStart,
},
agents::AgentsPage,
contact::ContactPage,
details::DetailsPage,
landing::LandingPage,
not_found::NotFoundPage,
search::SearchPage,
};
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
@ -29,7 +44,10 @@ pub enum Route {
#[at("/admin/contacts")]
AdminContacts,
#[at("/admin/edit/:edit_type/:edit_item")]
AdminEdit { edit_type: EditType, edit_item: EditItem },
AdminEdit {
edit_type: EditType,
edit_item: EditItem,
},
#[not_found]
#[at("/404")]
@ -50,6 +68,9 @@ pub fn switch(routes: Route) -> Html {
Route::AdminProjects => html! { <AdminProjects/> },
Route::AdminAgents => html! { <AdminAgents/> },
Route::AdminContacts => html! { <AdminContacts/> },
Route::AdminEdit { edit_type, edit_item } => html! { <AdminEditPage edit_item={edit_item} edit_type={edit_type}/> },
Route::AdminEdit {
edit_type,
edit_item,
} => html! { <AdminEditPage edit_item={edit_item} edit_type={edit_type}/> },
}
}
}

View File

@ -1 +1 @@
pub mod main_router;
pub mod main_router;

View File

@ -1,12 +1,13 @@
use super::storage;
pub fn get_admin_token_from_storage() -> Option<String> {
match storage::get_from_local_storage(storage::StorageKey::AdminUser) {
Ok(opt) => opt,
Err(_) => {
log::error!("No user stored when attempting to use admin panel. Redirect to admin panel.");
log::error!(
"No user stored when attempting to use admin panel. Redirect to admin panel."
);
None
},
}
}
}
}

View File

@ -0,0 +1 @@

View File

@ -1,5 +1,5 @@
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::{Event, HtmlInputElement, InputEvent, HtmlTextAreaElement};
use web_sys::{Event, HtmlInputElement, HtmlTextAreaElement, InputEvent};
pub fn get_value_from_input_event(e: InputEvent) -> String {
let event: Event = e.dyn_into().unwrap_throw();
@ -13,4 +13,4 @@ pub fn get_value_from_textarea_event(e: InputEvent) -> String {
let event_target = event.target().unwrap_throw();
let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw();
target.value()
}
}

View File

@ -1,4 +1,4 @@
pub mod admin_panel;
pub mod get_value;
pub mod input;
pub mod storage;
pub mod admin_panel;

View File

@ -1,6 +1,6 @@
use std::fmt::{Display};
use std::fmt::Display;
use err::{MessageResource, Error};
use err::{Error, MessageResource};
use web_sys::window;
pub enum StorageKey {
@ -52,4 +52,4 @@ pub fn get_from_local_storage(key: StorageKey) -> Result<Option<String>, Error>
},
None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))),
}
}
}