Implement calendar context menu with event creation modal
- Add CalendarContextMenu component for right-click on calendar days - Add CreateEventModal component with comprehensive event creation form - Integrate context menu detection to avoid conflicts between event/calendar menus - Add form validation and date/time selection with all-day toggle - Connect modal through component hierarchy from app to calendar 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										70
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								src/app.rs
									
									
									
									
									
								
							| @@ -2,8 +2,9 @@ use yew::prelude::*; | ||||
| use yew_router::prelude::*; | ||||
| use gloo_storage::{LocalStorage, Storage}; | ||||
| use web_sys::MouseEvent; | ||||
| use crate::components::{Sidebar, CreateCalendarModal, ContextMenu, EventContextMenu, RouteHandler}; | ||||
| use crate::components::{Sidebar, CreateCalendarModal, ContextMenu, EventContextMenu, CalendarContextMenu, CreateEventModal, EventCreationData, RouteHandler}; | ||||
| use crate::services::{CalendarService, calendar_service::{UserInfo, CalendarEvent}}; | ||||
| use chrono::NaiveDate; | ||||
|  | ||||
|  | ||||
| #[function_component] | ||||
| @@ -21,6 +22,11 @@ pub fn App() -> Html { | ||||
|     let event_context_menu_open = use_state(|| false); | ||||
|     let event_context_menu_pos = use_state(|| (0i32, 0i32)); | ||||
|     let event_context_menu_event = use_state(|| -> Option<CalendarEvent> { None }); | ||||
|     let calendar_context_menu_open = use_state(|| false); | ||||
|     let calendar_context_menu_pos = use_state(|| (0i32, 0i32)); | ||||
|     let calendar_context_menu_date = use_state(|| -> Option<NaiveDate> { None }); | ||||
|     let create_event_modal_open = use_state(|| false); | ||||
|     let selected_date_for_event = use_state(|| -> Option<NaiveDate> { None }); | ||||
|      | ||||
|     let available_colors = [ | ||||
|         "#3B82F6", "#10B981", "#F59E0B", "#EF4444",  | ||||
| @@ -103,10 +109,12 @@ pub fn App() -> Html { | ||||
|         let color_picker_open = color_picker_open.clone(); | ||||
|         let context_menu_open = context_menu_open.clone(); | ||||
|         let event_context_menu_open = event_context_menu_open.clone(); | ||||
|         let calendar_context_menu_open = calendar_context_menu_open.clone(); | ||||
|         Callback::from(move |_: MouseEvent| { | ||||
|             color_picker_open.set(None); | ||||
|             context_menu_open.set(false); | ||||
|             event_context_menu_open.set(false); | ||||
|             calendar_context_menu_open.set(false); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
| @@ -164,6 +172,41 @@ pub fn App() -> Html { | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_calendar_date_context_menu = { | ||||
|         let calendar_context_menu_open = calendar_context_menu_open.clone(); | ||||
|         let calendar_context_menu_pos = calendar_context_menu_pos.clone(); | ||||
|         let calendar_context_menu_date = calendar_context_menu_date.clone(); | ||||
|         Callback::from(move |(event, date): (MouseEvent, NaiveDate)| { | ||||
|             calendar_context_menu_open.set(true); | ||||
|             calendar_context_menu_pos.set((event.client_x(), event.client_y())); | ||||
|             calendar_context_menu_date.set(Some(date)); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_create_event_click = { | ||||
|         let create_event_modal_open = create_event_modal_open.clone(); | ||||
|         let selected_date_for_event = selected_date_for_event.clone(); | ||||
|         let calendar_context_menu_date = calendar_context_menu_date.clone(); | ||||
|         Callback::from(move |_: MouseEvent| { | ||||
|             create_event_modal_open.set(true); | ||||
|             selected_date_for_event.set((*calendar_context_menu_date).clone()); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_event_create = { | ||||
|         let create_event_modal_open = create_event_modal_open.clone(); | ||||
|         let auth_token = auth_token.clone(); | ||||
|         Callback::from(move |event_data: EventCreationData| { | ||||
|             web_sys::console::log_1(&format!("Creating event: {:?}", event_data).into()); | ||||
|             create_event_modal_open.set(false); | ||||
|             // TODO: Implement actual event creation API call | ||||
|             // For now, just close the modal and refresh | ||||
|             if (*auth_token).is_some() { | ||||
|                 web_sys::window().unwrap().location().reload().unwrap(); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let refresh_calendars = { | ||||
|         let auth_token = auth_token.clone(); | ||||
|         let user_info = user_info.clone(); | ||||
| @@ -234,6 +277,7 @@ pub fn App() -> Html { | ||||
|                                         user_info={(*user_info).clone()} | ||||
|                                         on_login={on_login.clone()} | ||||
|                                         on_event_context_menu={Some(on_event_context_menu.clone())} | ||||
|                                         on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())} | ||||
|                                     /> | ||||
|                                 </main> | ||||
|                             </> | ||||
| @@ -246,6 +290,7 @@ pub fn App() -> Html { | ||||
|                                     user_info={(*user_info).clone()} | ||||
|                                     on_login={on_login.clone()} | ||||
|                                     on_event_context_menu={Some(on_event_context_menu.clone())} | ||||
|                                     on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())} | ||||
|                                 /> | ||||
|                             </div> | ||||
|                         } | ||||
| @@ -357,7 +402,7 @@ pub fn App() -> Html { | ||||
|                         let refresh_calendars = refresh_calendars.clone(); | ||||
|                         move |_: MouseEvent| { | ||||
|                             if let (Some(token), Some(event)) = ((*auth_token).clone(), (*event_context_menu_event).clone()) { | ||||
|                                 let refresh_calendars = refresh_calendars.clone(); | ||||
|                                 let _refresh_calendars = refresh_calendars.clone(); | ||||
|                                 let event_context_menu_open = event_context_menu_open.clone(); | ||||
|                                  | ||||
|                                 wasm_bindgen_futures::spawn_local(async move { | ||||
| @@ -394,6 +439,27 @@ pub fn App() -> Html { | ||||
|                         } | ||||
|                     })} | ||||
|                 /> | ||||
|                  | ||||
|                 <CalendarContextMenu  | ||||
|                     is_open={*calendar_context_menu_open} | ||||
|                     x={calendar_context_menu_pos.0} | ||||
|                     y={calendar_context_menu_pos.1} | ||||
|                     on_close={Callback::from({ | ||||
|                         let calendar_context_menu_open = calendar_context_menu_open.clone(); | ||||
|                         move |_| calendar_context_menu_open.set(false) | ||||
|                     })} | ||||
|                     on_create_event={on_create_event_click} | ||||
|                 /> | ||||
|                  | ||||
|                 <CreateEventModal  | ||||
|                     is_open={*create_event_modal_open} | ||||
|                     selected_date={(*selected_date_for_event).clone()} | ||||
|                     on_close={Callback::from({ | ||||
|                         let create_event_modal_open = create_event_modal_open.clone(); | ||||
|                         move |_| create_event_modal_open.set(false) | ||||
|                     })} | ||||
|                     on_create={on_event_create} | ||||
|                 /> | ||||
|             </div> | ||||
|         </BrowserRouter> | ||||
|     } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ use chrono::{Datelike, Local, NaiveDate, Duration, Weekday}; | ||||
| use std::collections::HashMap; | ||||
| use crate::services::calendar_service::{CalendarEvent, UserInfo}; | ||||
| use crate::components::EventModal; | ||||
| use wasm_bindgen::JsCast; | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct CalendarProps { | ||||
| @@ -15,6 +16,8 @@ pub struct CalendarProps { | ||||
|     pub user_info: Option<UserInfo>, | ||||
|     #[prop_or_default] | ||||
|     pub on_event_context_menu: Option<Callback<(web_sys::MouseEvent, CalendarEvent)>>, | ||||
|     #[prop_or_default] | ||||
|     pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, NaiveDate)>>, | ||||
| } | ||||
|  | ||||
| #[function_component] | ||||
| @@ -115,9 +118,34 @@ pub fn Calendar(props: &CalendarProps) -> Html { | ||||
|                         let on_click = Callback::from(move |_| { | ||||
|                             selected_day_clone.set(date); | ||||
|                         }); | ||||
|  | ||||
|                         let on_context_menu = { | ||||
|                             let on_calendar_context_menu = props.on_calendar_context_menu.clone(); | ||||
|                             Callback::from(move |e: MouseEvent| { | ||||
|                                 // Only show context menu if we're not right-clicking on an event | ||||
|                                 if let Some(target) = e.target() { | ||||
|                                     if let Ok(element) = target.dyn_into::<web_sys::Element>() { | ||||
|                                         // Check if the click is on an event box or inside one | ||||
|                                         let mut current = Some(element); | ||||
|                                         while let Some(el) = current { | ||||
|                                             if el.class_name().contains("event-box") { | ||||
|                                                 return; // Don't show calendar context menu on events | ||||
|                                             } | ||||
|                                             current = el.parent_element(); | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                                  | ||||
|                                 e.prevent_default(); | ||||
|                                 e.stop_propagation(); | ||||
|                                 if let Some(callback) = &on_calendar_context_menu { | ||||
|                                     callback.emit((e, date)); | ||||
|                                 } | ||||
|                             }) | ||||
|                         }; | ||||
|                          | ||||
|                         html! { | ||||
|                             <div class={classes!(classes)} onclick={on_click}> | ||||
|                             <div class={classes!(classes)} onclick={on_click} oncontextmenu={on_context_menu}> | ||||
|                                 <div class="day-number">{day}</div> | ||||
|                                 { | ||||
|                                     if !events.is_empty() { | ||||
|   | ||||
							
								
								
									
										47
									
								
								src/components/calendar_context_menu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/calendar_context_menu.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| use yew::prelude::*; | ||||
| use web_sys::MouseEvent; | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct CalendarContextMenuProps { | ||||
|     pub is_open: bool, | ||||
|     pub x: i32, | ||||
|     pub y: i32, | ||||
|     pub on_close: Callback<()>, | ||||
|     pub on_create_event: Callback<MouseEvent>, | ||||
| } | ||||
|  | ||||
| #[function_component(CalendarContextMenu)] | ||||
| pub fn calendar_context_menu(props: &CalendarContextMenuProps) -> Html { | ||||
|     let menu_ref = use_node_ref(); | ||||
|      | ||||
|     if !props.is_open { | ||||
|         return html! {}; | ||||
|     } | ||||
|  | ||||
|     let style = format!( | ||||
|         "position: fixed; left: {}px; top: {}px; z-index: 1001;", | ||||
|         props.x, props.y | ||||
|     ); | ||||
|  | ||||
|     let on_create_event_click = { | ||||
|         let on_create_event = props.on_create_event.clone(); | ||||
|         let on_close = props.on_close.clone(); | ||||
|         Callback::from(move |e: MouseEvent| { | ||||
|             on_create_event.emit(e); | ||||
|             on_close.emit(()); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     html! { | ||||
|         <div  | ||||
|             ref={menu_ref} | ||||
|             class="context-menu"  | ||||
|             style={style} | ||||
|         > | ||||
|             <div class="context-menu-item context-menu-create" onclick={on_create_event_click}> | ||||
|                 <span class="context-menu-icon">{"+"}</span> | ||||
|                 {"Create Event"} | ||||
|             </div> | ||||
|         </div> | ||||
|     } | ||||
| } | ||||
							
								
								
									
										323
									
								
								src/components/create_event_modal.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								src/components/create_event_modal.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| use yew::prelude::*; | ||||
| use web_sys::{HtmlInputElement, HtmlTextAreaElement}; | ||||
| use chrono::{NaiveDate, NaiveTime}; | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct CreateEventModalProps { | ||||
|     pub is_open: bool, | ||||
|     pub selected_date: Option<NaiveDate>, | ||||
|     pub on_close: Callback<()>, | ||||
|     pub on_create: Callback<EventCreationData>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, PartialEq, Debug)] | ||||
| pub struct EventCreationData { | ||||
|     pub title: String, | ||||
|     pub description: String, | ||||
|     pub start_date: NaiveDate, | ||||
|     pub start_time: NaiveTime, | ||||
|     pub end_date: NaiveDate, | ||||
|     pub end_time: NaiveTime, | ||||
|     pub location: String, | ||||
|     pub all_day: bool, | ||||
| } | ||||
|  | ||||
| impl Default for EventCreationData { | ||||
|     fn default() -> Self { | ||||
|         let now = chrono::Local::now().naive_local(); | ||||
|         let start_time = NaiveTime::from_hms_opt(9, 0, 0).unwrap_or_default(); | ||||
|         let end_time = NaiveTime::from_hms_opt(10, 0, 0).unwrap_or_default(); | ||||
|          | ||||
|         Self { | ||||
|             title: String::new(), | ||||
|             description: String::new(), | ||||
|             start_date: now.date(), | ||||
|             start_time, | ||||
|             end_date: now.date(), | ||||
|             end_time, | ||||
|             location: String::new(), | ||||
|             all_day: false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[function_component(CreateEventModal)] | ||||
| pub fn create_event_modal(props: &CreateEventModalProps) -> Html { | ||||
|     let event_data = use_state(|| EventCreationData::default()); | ||||
|      | ||||
|     // Initialize with selected date if provided | ||||
|     use_effect_with((props.selected_date, props.is_open), { | ||||
|         let event_data = event_data.clone(); | ||||
|         move |(selected_date, is_open)| { | ||||
|             if *is_open { | ||||
|                 if let Some(date) = selected_date { | ||||
|                     let mut data = (*event_data).clone(); | ||||
|                     data.start_date = *date; | ||||
|                     data.end_date = *date; | ||||
|                     event_data.set(data); | ||||
|                 } else { | ||||
|                     event_data.set(EventCreationData::default()); | ||||
|                 } | ||||
|             } | ||||
|             || () | ||||
|         } | ||||
|     }); | ||||
|      | ||||
|     if !props.is_open { | ||||
|         return html! {}; | ||||
|     } | ||||
|  | ||||
|     let on_backdrop_click = { | ||||
|         let on_close = props.on_close.clone(); | ||||
|         Callback::from(move |e: MouseEvent| { | ||||
|             if e.target() == e.current_target() { | ||||
|                 on_close.emit(()); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_title_input = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: InputEvent| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 let mut data = (*event_data).clone(); | ||||
|                 data.title = input.value(); | ||||
|                 event_data.set(data); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_description_input = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: InputEvent| { | ||||
|             if let Some(textarea) = e.target_dyn_into::<HtmlTextAreaElement>() { | ||||
|                 let mut data = (*event_data).clone(); | ||||
|                 data.description = textarea.value(); | ||||
|                 event_data.set(data); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_location_input = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: InputEvent| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 let mut data = (*event_data).clone(); | ||||
|                 data.location = input.value(); | ||||
|                 event_data.set(data); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_start_date_change = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: Event| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 if let Ok(date) = NaiveDate::parse_from_str(&input.value(), "%Y-%m-%d") { | ||||
|                     let mut data = (*event_data).clone(); | ||||
|                     data.start_date = date; | ||||
|                     event_data.set(data); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_start_time_change = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: Event| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 if let Ok(time) = NaiveTime::parse_from_str(&input.value(), "%H:%M") { | ||||
|                     let mut data = (*event_data).clone(); | ||||
|                     data.start_time = time; | ||||
|                     event_data.set(data); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_end_date_change = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: Event| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 if let Ok(date) = NaiveDate::parse_from_str(&input.value(), "%Y-%m-%d") { | ||||
|                     let mut data = (*event_data).clone(); | ||||
|                     data.end_date = date; | ||||
|                     event_data.set(data); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_end_time_change = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: Event| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 if let Ok(time) = NaiveTime::parse_from_str(&input.value(), "%H:%M") { | ||||
|                     let mut data = (*event_data).clone(); | ||||
|                     data.end_time = time; | ||||
|                     event_data.set(data); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_all_day_change = { | ||||
|         let event_data = event_data.clone(); | ||||
|         Callback::from(move |e: Event| { | ||||
|             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||
|                 let mut data = (*event_data).clone(); | ||||
|                 data.all_day = input.checked(); | ||||
|                 event_data.set(data); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_create_click = { | ||||
|         let event_data = event_data.clone(); | ||||
|         let on_create = props.on_create.clone(); | ||||
|         Callback::from(move |_: MouseEvent| { | ||||
|             on_create.emit((*event_data).clone()); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let on_cancel_click = { | ||||
|         let on_close = props.on_close.clone(); | ||||
|         Callback::from(move |_: MouseEvent| { | ||||
|             on_close.emit(()); | ||||
|         }) | ||||
|     }; | ||||
|  | ||||
|     let data = &*event_data; | ||||
|  | ||||
|     html! { | ||||
|         <div class="modal-backdrop" onclick={on_backdrop_click}> | ||||
|             <div class="modal-content create-event-modal" onclick={Callback::from(|e: MouseEvent| e.stop_propagation())}> | ||||
|                 <div class="modal-header"> | ||||
|                     <h3>{"Create New Event"}</h3> | ||||
|                     <button type="button" class="modal-close" onclick={Callback::from({ | ||||
|                         let on_close = props.on_close.clone(); | ||||
|                         move |_: MouseEvent| on_close.emit(()) | ||||
|                     })}>{"×"}</button> | ||||
|                 </div> | ||||
|                  | ||||
|                 <div class="modal-body"> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="event-title">{"Title *"}</label> | ||||
|                         <input  | ||||
|                             type="text"  | ||||
|                             id="event-title" | ||||
|                             class="form-input"  | ||||
|                             value={data.title.clone()} | ||||
|                             oninput={on_title_input} | ||||
|                             placeholder="Enter event title" | ||||
|                             required=true | ||||
|                         /> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="form-group"> | ||||
|                         <label for="event-description">{"Description"}</label> | ||||
|                         <textarea  | ||||
|                             id="event-description" | ||||
|                             class="form-input"  | ||||
|                             value={data.description.clone()} | ||||
|                             oninput={on_description_input} | ||||
|                             placeholder="Enter event description" | ||||
|                             rows="3" | ||||
|                         ></textarea> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="form-group"> | ||||
|                         <label> | ||||
|                             <input  | ||||
|                                 type="checkbox"  | ||||
|                                 checked={data.all_day} | ||||
|                                 onchange={on_all_day_change} | ||||
|                             /> | ||||
|                             {" All Day"} | ||||
|                         </label> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="form-row"> | ||||
|                         <div class="form-group"> | ||||
|                             <label for="start-date">{"Start Date *"}</label> | ||||
|                             <input  | ||||
|                                 type="date"  | ||||
|                                 id="start-date" | ||||
|                                 class="form-input"  | ||||
|                                 value={data.start_date.format("%Y-%m-%d").to_string()} | ||||
|                                 onchange={on_start_date_change} | ||||
|                                 required=true | ||||
|                             /> | ||||
|                         </div> | ||||
|                          | ||||
|                         if !data.all_day { | ||||
|                             <div class="form-group"> | ||||
|                                 <label for="start-time">{"Start Time"}</label> | ||||
|                                 <input  | ||||
|                                     type="time"  | ||||
|                                     id="start-time" | ||||
|                                     class="form-input"  | ||||
|                                     value={data.start_time.format("%H:%M").to_string()} | ||||
|                                     onchange={on_start_time_change} | ||||
|                                 /> | ||||
|                             </div> | ||||
|                         } | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="form-row"> | ||||
|                         <div class="form-group"> | ||||
|                             <label for="end-date">{"End Date *"}</label> | ||||
|                             <input  | ||||
|                                 type="date"  | ||||
|                                 id="end-date" | ||||
|                                 class="form-input"  | ||||
|                                 value={data.end_date.format("%Y-%m-%d").to_string()} | ||||
|                                 onchange={on_end_date_change} | ||||
|                                 required=true | ||||
|                             /> | ||||
|                         </div> | ||||
|                          | ||||
|                         if !data.all_day { | ||||
|                             <div class="form-group"> | ||||
|                                 <label for="end-time">{"End Time"}</label> | ||||
|                                 <input  | ||||
|                                     type="time"  | ||||
|                                     id="end-time" | ||||
|                                     class="form-input"  | ||||
|                                     value={data.end_time.format("%H:%M").to_string()} | ||||
|                                     onchange={on_end_time_change} | ||||
|                                 /> | ||||
|                             </div> | ||||
|                         } | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="form-group"> | ||||
|                         <label for="event-location">{"Location"}</label> | ||||
|                         <input  | ||||
|                             type="text"  | ||||
|                             id="event-location" | ||||
|                             class="form-input"  | ||||
|                             value={data.location.clone()} | ||||
|                             oninput={on_location_input} | ||||
|                             placeholder="Enter event location" | ||||
|                         /> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                  | ||||
|                 <div class="modal-footer"> | ||||
|                     <button type="button" class="btn btn-secondary" onclick={on_cancel_click}> | ||||
|                         {"Cancel"} | ||||
|                     </button> | ||||
|                     <button  | ||||
|                         type="button"  | ||||
|                         class="btn btn-primary"  | ||||
|                         onclick={on_create_click} | ||||
|                         disabled={data.title.trim().is_empty()} | ||||
|                     > | ||||
|                         {"Create Event"} | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     } | ||||
| } | ||||
| @@ -4,6 +4,8 @@ pub mod event_modal; | ||||
| pub mod create_calendar_modal; | ||||
| pub mod context_menu; | ||||
| pub mod event_context_menu; | ||||
| pub mod calendar_context_menu; | ||||
| pub mod create_event_modal; | ||||
| pub mod sidebar; | ||||
| pub mod calendar_list_item; | ||||
| pub mod route_handler; | ||||
| @@ -14,6 +16,8 @@ pub use event_modal::EventModal; | ||||
| pub use create_calendar_modal::CreateCalendarModal; | ||||
| pub use context_menu::ContextMenu; | ||||
| pub use event_context_menu::EventContextMenu; | ||||
| pub use calendar_context_menu::CalendarContextMenu; | ||||
| pub use create_event_modal::{CreateEventModal, EventCreationData}; | ||||
| pub use sidebar::Sidebar; | ||||
| pub use calendar_list_item::CalendarListItem; | ||||
| pub use route_handler::RouteHandler; | ||||
| @@ -20,6 +20,8 @@ pub struct RouteHandlerProps { | ||||
|     pub on_login: Callback<String>, | ||||
|     #[prop_or_default] | ||||
|     pub on_event_context_menu: Option<Callback<(web_sys::MouseEvent, CalendarEvent)>>, | ||||
|     #[prop_or_default] | ||||
|     pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, chrono::NaiveDate)>>, | ||||
| } | ||||
|  | ||||
| #[function_component(RouteHandler)] | ||||
| @@ -28,6 +30,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html { | ||||
|     let user_info = props.user_info.clone(); | ||||
|     let on_login = props.on_login.clone(); | ||||
|     let on_event_context_menu = props.on_event_context_menu.clone(); | ||||
|     let on_calendar_context_menu = props.on_calendar_context_menu.clone(); | ||||
|      | ||||
|     html! { | ||||
|         <Switch<Route> render={move |route| { | ||||
| @@ -35,6 +38,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html { | ||||
|             let user_info = user_info.clone(); | ||||
|             let on_login = on_login.clone(); | ||||
|             let on_event_context_menu = on_event_context_menu.clone(); | ||||
|             let on_calendar_context_menu = on_calendar_context_menu.clone(); | ||||
|              | ||||
|             match route { | ||||
|                 Route::Home => { | ||||
| @@ -57,6 +61,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html { | ||||
|                             <CalendarView  | ||||
|                                 user_info={user_info}  | ||||
|                                 on_event_context_menu={on_event_context_menu} | ||||
|                                 on_calendar_context_menu={on_calendar_context_menu} | ||||
|                             />  | ||||
|                         } | ||||
|                     } else { | ||||
| @@ -73,6 +78,8 @@ pub struct CalendarViewProps { | ||||
|     pub user_info: Option<UserInfo>, | ||||
|     #[prop_or_default] | ||||
|     pub on_event_context_menu: Option<Callback<(web_sys::MouseEvent, CalendarEvent)>>, | ||||
|     #[prop_or_default] | ||||
|     pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, chrono::NaiveDate)>>, | ||||
| } | ||||
|  | ||||
| use gloo_storage::{LocalStorage, Storage}; | ||||
| @@ -230,6 +237,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html { | ||||
|                                 refreshing_event_uid={(*refreshing_event).clone()}  | ||||
|                                 user_info={props.user_info.clone()} | ||||
|                                 on_event_context_menu={props.on_event_context_menu.clone()} | ||||
|                                 on_calendar_context_menu={props.on_calendar_context_menu.clone()} | ||||
|                             /> | ||||
|                         </div> | ||||
|                     } | ||||
| @@ -241,6 +249,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html { | ||||
|                             refreshing_event_uid={(*refreshing_event).clone()}  | ||||
|                             user_info={props.user_info.clone()} | ||||
|                             on_event_context_menu={props.on_event_context_menu.clone()} | ||||
|                             on_calendar_context_menu={props.on_calendar_context_menu.clone()} | ||||
|                         /> | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone