Compare commits
	
		
			3 Commits
		
	
	
		
			38b22287c7
			...
			d930468748
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d930468748 | |||
|   | 91be4436a9 | ||
|   | 927cd7d2bb | 
| @@ -78,17 +78,75 @@ pub async fn fetch_external_calendar_events( | |||||||
|  |  | ||||||
|     // If not fetched from cache, get from external URL |     // If not fetched from cache, get from external URL | ||||||
|     if !fetched_from_cache { |     if !fetched_from_cache { | ||||||
|         let client = Client::new(); |         // Log the URL being fetched for debugging | ||||||
|         let response = client |         println!("🌍 Fetching calendar URL: {}", calendar.url); | ||||||
|             .get(&calendar.url) |  | ||||||
|             .send() |  | ||||||
|             .await |  | ||||||
|             .map_err(|e| ApiError::Internal(format!("Failed to fetch calendar: {}", e)))?; |  | ||||||
|          |          | ||||||
|         if !response.status().is_success() { |         let user_agents = vec![ | ||||||
|             return Err(ApiError::Internal(format!("Calendar server returned: {}", response.status()))); |             "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", | ||||||
|  |             "Mozilla/5.0 (compatible; Runway Calendar/1.0)", | ||||||
|  |             "Outlook-iOS/709.2226530.prod.iphone (3.24.1)" | ||||||
|  |         ]; | ||||||
|  |          | ||||||
|  |         let mut response = None; | ||||||
|  |         let mut last_error = None; | ||||||
|  |          | ||||||
|  |         // Try different user agents | ||||||
|  |         for (i, ua) in user_agents.iter().enumerate() { | ||||||
|  |             println!("🔄 Attempt {} with User-Agent: {}", i + 1, ua); | ||||||
|  |              | ||||||
|  |             let client = Client::builder() | ||||||
|  |                 .redirect(reqwest::redirect::Policy::limited(10)) | ||||||
|  |                 .timeout(std::time::Duration::from_secs(30)) | ||||||
|  |                 .user_agent(*ua) | ||||||
|  |                 .build() | ||||||
|  |                 .map_err(|e| ApiError::Internal(format!("Failed to create HTTP client: {}", e)))?; | ||||||
|  |              | ||||||
|  |             let result = client | ||||||
|  |                 .get(&calendar.url) | ||||||
|  |                 .header("Accept", "text/calendar,application/calendar+xml,text/plain,*/*") | ||||||
|  |                 .header("Accept-Charset", "utf-8") | ||||||
|  |                 .header("Cache-Control", "no-cache") | ||||||
|  |                 .send() | ||||||
|  |                 .await; | ||||||
|  |                  | ||||||
|  |             match result { | ||||||
|  |                 Ok(resp) => { | ||||||
|  |                     let status = resp.status(); | ||||||
|  |                     println!("📡 Response status: {}", status); | ||||||
|  |                     if status.is_success() { | ||||||
|  |                         response = Some(resp); | ||||||
|  |                         break; | ||||||
|  |                     } else if status == 400 { | ||||||
|  |                         // Check if this is an Outlook auth error | ||||||
|  |                         let error_body = resp.text().await.unwrap_or_default(); | ||||||
|  |                         if error_body.contains("OwaPage") || error_body.contains("Outlook") { | ||||||
|  |                             println!("🚫 Outlook authentication error detected, trying next approach..."); | ||||||
|  |                             last_error = Some(format!("Outlook auth error: {}", error_body.chars().take(100).collect::<String>())); | ||||||
|  |                             continue; | ||||||
|  |                         } | ||||||
|  |                         last_error = Some(format!("Bad Request: {}", error_body.chars().take(100).collect::<String>())); | ||||||
|  |                     } else { | ||||||
|  |                         last_error = Some(format!("HTTP {}", status)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 Err(e) => { | ||||||
|  |                     println!("❌ Request failed: {}", e); | ||||||
|  |                     last_error = Some(format!("Request error: {}", e)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         let response = response.ok_or_else(|| { | ||||||
|  |             ApiError::Internal(format!( | ||||||
|  |                 "Failed to fetch calendar after trying {} different approaches. Last error: {}",  | ||||||
|  |                 user_agents.len(),  | ||||||
|  |                 last_error.unwrap_or("Unknown error".to_string()) | ||||||
|  |             )) | ||||||
|  |         })?; | ||||||
|  |  | ||||||
|  |         // Response is guaranteed to be successful here since we checked in the loop | ||||||
|  |         println!("✅ Successfully fetched calendar data"); | ||||||
|  |          | ||||||
|         ics_content = response |         ics_content = response | ||||||
|             .text() |             .text() | ||||||
|             .await |             .await | ||||||
|   | |||||||
| @@ -567,19 +567,60 @@ pub fn App() -> Html { | |||||||
|  |  | ||||||
|     let on_color_change = { |     let on_color_change = { | ||||||
|         let user_info = user_info.clone(); |         let user_info = user_info.clone(); | ||||||
|  |         let external_calendars = external_calendars.clone(); | ||||||
|         let color_picker_open = color_picker_open.clone(); |         let color_picker_open = color_picker_open.clone(); | ||||||
|         Callback::from(move |(calendar_path, color): (String, String)| { |         Callback::from(move |(calendar_path, color): (String, String)| { | ||||||
|             if let Some(mut info) = (*user_info).clone() { |             if calendar_path.starts_with("external_") { | ||||||
|                 for calendar in &mut info.calendars { |                 // Handle external calendar color change | ||||||
|                     if calendar.path == calendar_path { |                 if let Ok(id_str) = calendar_path.strip_prefix("external_").unwrap_or("").parse::<i32>() { | ||||||
|                         calendar.color = color.clone(); |                     let external_calendars = external_calendars.clone(); | ||||||
|                         break; |                     let color = color.clone(); | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 user_info.set(Some(info.clone())); |  | ||||||
|                      |                      | ||||||
|                 if let Ok(json) = serde_json::to_string(&info) { |                     wasm_bindgen_futures::spawn_local(async move { | ||||||
|                     let _ = LocalStorage::set("calendar_colors", json); |                         // Find the external calendar to get its current details | ||||||
|  |                         if let Some(cal) = (*external_calendars).iter().find(|c| c.id == id_str) { | ||||||
|  |                             match CalendarService::update_external_calendar( | ||||||
|  |                                 id_str, | ||||||
|  |                                 &cal.name, | ||||||
|  |                                 &cal.url, | ||||||
|  |                                 &color, | ||||||
|  |                                 cal.is_visible, | ||||||
|  |                             ).await { | ||||||
|  |                                 Ok(_) => { | ||||||
|  |                                     // Update the local state | ||||||
|  |                                     let mut updated_calendars = (*external_calendars).clone(); | ||||||
|  |                                     for calendar in &mut updated_calendars { | ||||||
|  |                                         if calendar.id == id_str { | ||||||
|  |                                             calendar.color = color.clone(); | ||||||
|  |                                             break; | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                     external_calendars.set(updated_calendars); | ||||||
|  |                                      | ||||||
|  |                                     // No need to refresh events - they will automatically pick up the new color | ||||||
|  |                                     // from the calendar when rendered since they use the same calendar_path matching | ||||||
|  |                                 } | ||||||
|  |                                 Err(e) => { | ||||||
|  |                                     web_sys::console::error_1(&format!("Failed to update external calendar color: {}", e).into()); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 // Handle CalDAV calendar color change (existing logic) | ||||||
|  |                 if let Some(mut info) = (*user_info).clone() { | ||||||
|  |                     for calendar in &mut info.calendars { | ||||||
|  |                         if calendar.path == calendar_path { | ||||||
|  |                             calendar.color = color.clone(); | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     user_info.set(Some(info.clone())); | ||||||
|  |  | ||||||
|  |                     if let Ok(json) = serde_json::to_string(&info) { | ||||||
|  |                         let _ = LocalStorage::set("calendar_colors", json); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             color_picker_open.set(None); |             color_picker_open.set(None); | ||||||
|   | |||||||
| @@ -30,8 +30,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|     let is_creating = use_state(|| false); |     let is_creating = use_state(|| false); | ||||||
|      |      | ||||||
|     // External Calendar state |     // External Calendar state | ||||||
|     let external_name_ref = use_node_ref(); |     let external_name = use_state(|| String::new()); | ||||||
|     let external_url_ref = use_node_ref(); |     let external_url = use_state(|| String::new()); | ||||||
|     let external_selected_color = use_state(|| Some("#4285f4".to_string())); |     let external_selected_color = use_state(|| Some("#4285f4".to_string())); | ||||||
|     let external_is_loading = use_state(|| false); |     let external_is_loading = use_state(|| false); | ||||||
|     let external_error_message = use_state(|| None::<String>); |     let external_error_message = use_state(|| None::<String>); | ||||||
| @@ -43,6 +43,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|         let selected_color = selected_color.clone(); |         let selected_color = selected_color.clone(); | ||||||
|         let create_error_message = create_error_message.clone(); |         let create_error_message = create_error_message.clone(); | ||||||
|         let is_creating = is_creating.clone(); |         let is_creating = is_creating.clone(); | ||||||
|  |         let external_name = external_name.clone(); | ||||||
|  |         let external_url = external_url.clone(); | ||||||
|         let external_is_loading = external_is_loading.clone(); |         let external_is_loading = external_is_loading.clone(); | ||||||
|         let external_error_message = external_error_message.clone(); |         let external_error_message = external_error_message.clone(); | ||||||
|         let external_selected_color = external_selected_color.clone(); |         let external_selected_color = external_selected_color.clone(); | ||||||
| @@ -56,6 +58,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|                 selected_color.set(None); |                 selected_color.set(None); | ||||||
|                 create_error_message.set(None); |                 create_error_message.set(None); | ||||||
|                 is_creating.set(false); |                 is_creating.set(false); | ||||||
|  |                 external_name.set(String::new()); | ||||||
|  |                 external_url.set(String::new()); | ||||||
|                 external_is_loading.set(false); |                 external_is_loading.set(false); | ||||||
|                 external_error_message.set(None); |                 external_error_message.set(None); | ||||||
|                 external_selected_color.set(Some("#4285f4".to_string())); |                 external_selected_color.set(Some("#4285f4".to_string())); | ||||||
| @@ -146,8 +150,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|  |  | ||||||
|     // External Calendar handlers |     // External Calendar handlers | ||||||
|     let on_external_submit = { |     let on_external_submit = { | ||||||
|         let external_name_ref = external_name_ref.clone(); |         let external_name = external_name.clone(); | ||||||
|         let external_url_ref = external_url_ref.clone(); |         let external_url = external_url.clone(); | ||||||
|         let external_selected_color = external_selected_color.clone(); |         let external_selected_color = external_selected_color.clone(); | ||||||
|         let external_is_loading = external_is_loading.clone(); |         let external_is_loading = external_is_loading.clone(); | ||||||
|         let external_error_message = external_error_message.clone(); |         let external_error_message = external_error_message.clone(); | ||||||
| @@ -157,24 +161,28 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|         Callback::from(move |e: SubmitEvent| { |         Callback::from(move |e: SubmitEvent| { | ||||||
|             e.prevent_default(); |             e.prevent_default(); | ||||||
|              |              | ||||||
|             let name = external_name_ref |             let name = (*external_name).trim().to_string(); | ||||||
|                 .cast::<HtmlInputElement>() |             let url = (*external_url).trim().to_string(); | ||||||
|                 .map(|input| input.value()) |  | ||||||
|                 .unwrap_or_default() |  | ||||||
|                 .trim() |  | ||||||
|                 .to_string(); |  | ||||||
|              |  | ||||||
|             let url = external_url_ref |  | ||||||
|                 .cast::<HtmlInputElement>() |  | ||||||
|                 .map(|input| input.value()) |  | ||||||
|                 .unwrap_or_default() |  | ||||||
|                 .trim() |  | ||||||
|                 .to_string(); |  | ||||||
|                  |  | ||||||
|             let color = (*external_selected_color).clone().unwrap_or_else(|| "#4285f4".to_string()); |             let color = (*external_selected_color).clone().unwrap_or_else(|| "#4285f4".to_string()); | ||||||
|  |  | ||||||
|             if name.is_empty() || url.is_empty() { |             // Debug logging to understand the issue | ||||||
|                 external_error_message.set(Some("Name and URL are required".to_string())); |             web_sys::console::log_1(&format!("External calendar form submission - Name: '{}', URL: '{}'", name, url).into()); | ||||||
|  |  | ||||||
|  |             if name.is_empty() { | ||||||
|  |                 external_error_message.set(Some("Calendar name is required".to_string())); | ||||||
|  |                 web_sys::console::log_1(&"Validation failed: empty name".into()); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if url.is_empty() { | ||||||
|  |                 external_error_message.set(Some("Calendar URL is required".to_string())); | ||||||
|  |                 web_sys::console::log_1(&"Validation failed: empty URL".into()); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Basic URL validation | ||||||
|  |             if !url.starts_with("http://") && !url.starts_with("https://") { | ||||||
|  |                 external_error_message.set(Some("Please enter a valid HTTP or HTTPS URL".to_string())); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -204,6 +212,25 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     // External input change handlers | ||||||
|  |     let on_external_name_change = { | ||||||
|  |         let external_name = external_name.clone(); | ||||||
|  |         Callback::from(move |e: Event| { | ||||||
|  |             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||||
|  |                 external_name.set(input.value()); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_external_url_change = { | ||||||
|  |         let external_url = external_url.clone(); | ||||||
|  |         Callback::from(move |e: Event| { | ||||||
|  |             if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { | ||||||
|  |                 external_url.set(input.value()); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     if !props.is_open { |     if !props.is_open { | ||||||
|         return html! {}; |         return html! {}; | ||||||
|     } |     } | ||||||
| @@ -333,7 +360,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|                                         <input |                                         <input | ||||||
|                                             type="text" |                                             type="text" | ||||||
|                                             id="external-name" |                                             id="external-name" | ||||||
|                                             ref={external_name_ref.clone()} |                                             value={(*external_name).clone()} | ||||||
|  |                                             onchange={on_external_name_change} | ||||||
|                                             placeholder="Enter calendar name" |                                             placeholder="Enter calendar name" | ||||||
|                                             disabled={*external_is_loading} |                                             disabled={*external_is_loading} | ||||||
|                                         /> |                                         /> | ||||||
| @@ -344,7 +372,8 @@ pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | |||||||
|                                         <input |                                         <input | ||||||
|                                             type="url" |                                             type="url" | ||||||
|                                             id="external-url" |                                             id="external-url" | ||||||
|                                             ref={external_url_ref.clone()} |                                             value={(*external_url).clone()} | ||||||
|  |                                             onchange={on_external_url_change} | ||||||
|                                             placeholder="https://example.com/calendar.ics" |                                             placeholder="https://example.com/calendar.ics" | ||||||
|                                             disabled={*external_is_loading} |                                             disabled={*external_is_loading} | ||||||
|                                         /> |                                         /> | ||||||
|   | |||||||
| @@ -256,7 +256,11 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                                         html! { |                                         html! { | ||||||
|                                             <li class="external-calendar-item" style="position: relative;"> |                                             <li class="external-calendar-item" style="position: relative;"> | ||||||
|                                                 <div  |                                                 <div  | ||||||
|                                                     class="external-calendar-info" |                                                     class={if props.color_picker_open.as_ref() == Some(&format!("external_{}", cal.id)) {  | ||||||
|  |                                                         "external-calendar-info color-picker-active"  | ||||||
|  |                                                     } else {  | ||||||
|  |                                                         "external-calendar-info"  | ||||||
|  |                                                     }} | ||||||
|                                                     oncontextmenu={{ |                                                     oncontextmenu={{ | ||||||
|                                                         let on_context_menu = on_external_calendar_context_menu.clone(); |                                                         let on_context_menu = on_external_calendar_context_menu.clone(); | ||||||
|                                                         let cal_id = cal.id; |                                                         let cal_id = cal.id; | ||||||
| @@ -273,7 +277,48 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                                                     <span  |                                                     <span  | ||||||
|                                                         class="external-calendar-color"  |                                                         class="external-calendar-color"  | ||||||
|                                                         style={format!("background-color: {}", cal.color)} |                                                         style={format!("background-color: {}", cal.color)} | ||||||
|                                                     /> |                                                         onclick={{ | ||||||
|  |                                                             let on_color_picker_toggle = props.on_color_picker_toggle.clone(); | ||||||
|  |                                                             let external_id = format!("external_{}", cal.id); | ||||||
|  |                                                             Callback::from(move |e: MouseEvent| { | ||||||
|  |                                                                 e.stop_propagation(); | ||||||
|  |                                                                 on_color_picker_toggle.emit(external_id.clone()); | ||||||
|  |                                                             }) | ||||||
|  |                                                         }} | ||||||
|  |                                                     > | ||||||
|  |                                                         { | ||||||
|  |                                                             if props.color_picker_open.as_ref() == Some(&format!("external_{}", cal.id)) { | ||||||
|  |                                                                 html! { | ||||||
|  |                                                                     <div class="color-picker-dropdown"> | ||||||
|  |                                                                         { | ||||||
|  |                                                                             props.available_colors.iter().map(|color| { | ||||||
|  |                                                                                 let color_str = color.clone(); | ||||||
|  |                                                                                 let external_id = format!("external_{}", cal.id); | ||||||
|  |                                                                                 let on_color_change = props.on_color_change.clone(); | ||||||
|  |                                                                                  | ||||||
|  |                                                                                 let on_color_select = Callback::from(move |_: MouseEvent| { | ||||||
|  |                                                                                     on_color_change.emit((external_id.clone(), color_str.clone())); | ||||||
|  |                                                                                 }); | ||||||
|  |                                                                                  | ||||||
|  |                                                                                 let is_selected = cal.color == *color; | ||||||
|  |                                                                                  | ||||||
|  |                                                                                 html! { | ||||||
|  |                                                                                     <div | ||||||
|  |                                                                                         key={color.clone()} | ||||||
|  |                                                                                         class={if is_selected { "color-option selected" } else { "color-option" }} | ||||||
|  |                                                                                         style={format!("background-color: {}", color)} | ||||||
|  |                                                                                         onclick={on_color_select} | ||||||
|  |                                                                                     /> | ||||||
|  |                                                                                 } | ||||||
|  |                                                                             }).collect::<Html>() | ||||||
|  |                                                                         } | ||||||
|  |                                                                     </div> | ||||||
|  |                                                                 } | ||||||
|  |                                                             } else { | ||||||
|  |                                                                 html! {} | ||||||
|  |                                                             } | ||||||
|  |                                                         } | ||||||
|  |                                                     </span> | ||||||
|                                                     <span class="external-calendar-name">{&cal.name}</span> |                                                     <span class="external-calendar-name">{&cal.name}</span> | ||||||
|                                                     <div class="external-calendar-actions"> |                                                     <div class="external-calendar-actions"> | ||||||
|                                                         { |                                                         { | ||||||
|   | |||||||
| @@ -281,7 +281,7 @@ body { | |||||||
|     border-radius: 4px; |     border-radius: 4px; | ||||||
|     padding: 1rem; |     padding: 1rem; | ||||||
|     box-shadow: 0 4px 12px rgba(0,0,0,0.15); |     box-shadow: 0 4px 12px rgba(0,0,0,0.15); | ||||||
|     z-index: 1000; |     z-index: 999999; | ||||||
|     display: grid; |     display: grid; | ||||||
|     grid-template-columns: repeat(4, 1fr); |     grid-template-columns: repeat(4, 1fr); | ||||||
|     gap: 0.75rem; |     gap: 0.75rem; | ||||||
| @@ -3677,7 +3677,7 @@ body { | |||||||
|     border: 1px solid var(--glass-bg); |     border: 1px solid var(--glass-bg); | ||||||
| } | } | ||||||
|  |  | ||||||
| .external-calendar-info:hover { | .external-calendar-info:hover:not(.color-picker-active) { | ||||||
|     background: var(--glass-bg); |     background: var(--glass-bg); | ||||||
|     border-color: var(--glass-bg-light); |     border-color: var(--glass-bg-light); | ||||||
|     transform: translateX(2px); |     transform: translateX(2px); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user