leptos_shadcn_error_boundary/
lib.rs1use leptos::prelude::*;
7use std::panic::PanicHookInfo;
8
9#[derive(Clone, Debug)]
11pub struct ErrorInfo {
12 pub message: String,
14 pub technical_details: Option<String>,
16}
17
18#[component]
20pub fn ErrorFallback(
21 #[prop(into)] error_info: ErrorInfo,
22) -> impl IntoView {
23 view! {
24 <div class="error-fallback">
25 <div class="error-content">
26 <div class="error-icon">!</div>
27 <h2 class="error-title">Something went wrong</h2>
28 <p class="error-message">
29 {error_info.message}
30 </p>
31 <div class="error-actions">
32 <button
33 class="error-retry"
34 on:click=move |_| {
35 if let Some(window) = web_sys::window() {
37 let _ = window.location().reload();
38 }
39 }
40 >
41 "Try Again"
42 </button>
43 </div>
44 </div>
45 </div>
46 }
47}
48
49#[component]
51pub fn ErrorBoundary(
52 #[prop(into)] children: Children,
53) -> impl IntoView {
54 let (has_error, set_has_error) = signal(false);
55 let (_error_info, set_error_info) = signal(None::<ErrorInfo>);
56
57 std::panic::set_hook(Box::new(move |panic_info: &PanicHookInfo<'_>| {
59 log::error!("Panic caught: {:?}", panic_info);
60
61 let error = ErrorInfo {
62 message: "An unexpected error occurred. Please try refreshing the page.".to_string(),
63 technical_details: Some(format!("{:?}", panic_info)),
64 };
65
66 set_error_info.set(Some(error));
67 set_has_error.set(true);
68 }));
69
70 if has_error.get() {
72 if let Some(error) = _error_info.get() {
73 view! { <ErrorFallback error_info=error /> }.into_any()
74 } else {
75 view! { <ErrorFallback error_info=ErrorInfo { message: "An error occurred".to_string(), technical_details: None } /> }.into_any()
76 }
77 } else {
78 children()
79 }
80}
81
82pub fn use_error_handler() -> (ReadSignal<bool>, WriteSignal<bool>, WriteSignal<Option<ErrorInfo>>) {
84 let (has_error, set_has_error) = signal(false);
85 let (_error_info, set_error_info) = signal(None::<ErrorInfo>);
86
87 (has_error, set_has_error, set_error_info)
88}
89
90pub fn create_user_error(message: &str, technical: Option<&str>) -> ErrorInfo {
92 ErrorInfo {
93 message: message.to_string(),
94 technical_details: technical.map(|s| s.to_string()),
95 }
96}
97
98pub fn handle_error<T>(result: Result<T, impl std::fmt::Debug>) -> Option<T> {
100 match result {
101 Ok(value) => Some(value),
102 Err(error) => {
103 log::error!("Error occurred: {:?}", error);
104 None
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_error_info_creation() {
115 let error = ErrorInfo {
116 message: "Test error".to_string(),
117 technical_details: Some("Technical details".to_string()),
118 };
119
120 assert_eq!(error.message, "Test error");
121 assert_eq!(error.technical_details, Some("Technical details".to_string()));
122 }
123
124 #[test]
125 fn test_create_user_error() {
126 let error = create_user_error("User message", Some("Technical"));
127 assert_eq!(error.message, "User message");
128 assert_eq!(error.technical_details, Some("Technical".to_string()));
129 }
130
131 #[test]
132 fn test_handle_error() {
133 let result: Result<i32, &str> = Ok(42);
134 assert_eq!(handle_error(result), Some(42));
135
136 let error_result: Result<i32, &str> = Err("Error");
137 assert_eq!(handle_error(error_result), None);
138 }
139
140 #[test]
141 fn test_error_info_without_technical_details() {
142 let error = ErrorInfo {
143 message: "Simple error".to_string(),
144 technical_details: None,
145 };
146
147 assert_eq!(error.message, "Simple error");
148 assert_eq!(error.technical_details, None);
149 }
150
151 #[test]
152 fn test_create_user_error_without_technical() {
153 let error = create_user_error("User message", None);
154 assert_eq!(error.message, "User message");
155 assert_eq!(error.technical_details, None);
156 }
157
158 #[test]
159 fn test_use_error_handler() {
160 let (has_error, set_has_error, set_error_info) = use_error_handler();
161
162 assert!(!has_error.get());
164
165 let error = ErrorInfo {
167 message: "Test error".to_string(),
168 technical_details: None,
169 };
170 set_error_info.set(Some(error));
171 set_has_error.set(true);
172
173 assert!(has_error.get());
175 }
176
177 #[test]
178 fn test_error_info_clone_and_debug() {
179 let error = ErrorInfo {
180 message: "Test error".to_string(),
181 technical_details: Some("Technical".to_string()),
182 };
183
184 let cloned = error.clone();
185 assert_eq!(error.message, cloned.message);
186 assert_eq!(error.technical_details, cloned.technical_details);
187
188 let debug_str = format!("{:?}", error);
190 assert!(debug_str.contains("Test error"));
191 assert!(debug_str.contains("Technical"));
192 }
193}