fix(frontend): use signal-based toast for async contexts
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos::html::Dialog;
|
use leptos::html::Dialog;
|
||||||
use crate::store::{toast_success, toast_error, toast_warning};
|
use crate::store::{show_toast_with_signal, TorrentStore};
|
||||||
|
use shared::NotificationLevel;
|
||||||
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@@ -8,6 +9,9 @@ pub fn AddTorrentModal(
|
|||||||
#[prop(into)]
|
#[prop(into)]
|
||||||
on_close: Callback<()>,
|
on_close: Callback<()>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
|
let store = use_context::<TorrentStore>().expect("TorrentStore not provided");
|
||||||
|
let notifications = store.notifications;
|
||||||
|
|
||||||
let dialog_ref = create_node_ref::<Dialog>();
|
let dialog_ref = create_node_ref::<Dialog>();
|
||||||
let (uri, set_uri) = create_signal(String::new());
|
let (uri, set_uri) = create_signal(String::new());
|
||||||
let (is_loading, set_loading) = create_signal(false);
|
let (is_loading, set_loading) = create_signal(false);
|
||||||
@@ -23,7 +27,7 @@ pub fn AddTorrentModal(
|
|||||||
let handle_submit = move |_| {
|
let handle_submit = move |_| {
|
||||||
let uri_val = uri.get();
|
let uri_val = uri.get();
|
||||||
if uri_val.is_empty() {
|
if uri_val.is_empty() {
|
||||||
toast_warning("Lütfen bir Magnet URI veya URL girin");
|
show_toast_with_signal(notifications, NotificationLevel::Warning, "Lütfen bir Magnet URI veya URL girin");
|
||||||
set_error_msg.set(Some("Please enter a Magnet URI or URL".to_string()));
|
set_error_msg.set(Some("Please enter a Magnet URI or URL".to_string()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,7 +48,7 @@ pub fn AddTorrentModal(
|
|||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if resp.ok() {
|
if resp.ok() {
|
||||||
logging::log!("Torrent added successfully");
|
logging::log!("Torrent added successfully");
|
||||||
toast_success("Torrent eklendi");
|
show_toast_with_signal(notifications, NotificationLevel::Success, "Torrent eklendi");
|
||||||
set_loading.set(false);
|
set_loading.set(false);
|
||||||
if let Some(dialog) = dialog_ref.get() {
|
if let Some(dialog) = dialog_ref.get() {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
@@ -54,14 +58,14 @@ pub fn AddTorrentModal(
|
|||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let text = resp.text().await.unwrap_or_default();
|
let text = resp.text().await.unwrap_or_default();
|
||||||
logging::error!("Failed to add torrent: {} - {}", status, text);
|
logging::error!("Failed to add torrent: {} - {}", status, text);
|
||||||
toast_error("Torrent eklenemedi");
|
show_toast_with_signal(notifications, NotificationLevel::Error, "Torrent eklenemedi");
|
||||||
set_error_msg.set(Some(format!("Error {}: {}", status, text)));
|
set_error_msg.set(Some(format!("Error {}: {}", status, text)));
|
||||||
set_loading.set(false);
|
set_loading.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging::error!("Network error: {}", e);
|
logging::error!("Network error: {}", e);
|
||||||
toast_error("Bağlantı hatası");
|
show_toast_with_signal(notifications, NotificationLevel::Error, "Bağlantı hatası");
|
||||||
set_error_msg.set(Some(format!("Network Error: {}", e)));
|
set_error_msg.set(Some(format!("Network Error: {}", e)));
|
||||||
set_loading.set(false);
|
set_loading.set(false);
|
||||||
}
|
}
|
||||||
@@ -69,7 +73,7 @@ pub fn AddTorrentModal(
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging::error!("Serialization error: {}", e);
|
logging::error!("Serialization error: {}", e);
|
||||||
toast_error("İstek hatası");
|
show_toast_with_signal(notifications, NotificationLevel::Error, "İstek hatası");
|
||||||
set_error_msg.set(Some(format!("Request Error: {}", e)));
|
set_error_msg.set(Some(format!("Request Error: {}", e)));
|
||||||
set_loading.set(false);
|
set_loading.set(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use leptos::*;
|
use leptos::*;
|
||||||
use wasm_bindgen::closure::Closure;
|
use wasm_bindgen::closure::Closure;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use crate::store::{get_action_messages, toast_success, toast_error};
|
use crate::store::{get_action_messages, show_toast_with_signal};
|
||||||
|
use shared::NotificationLevel;
|
||||||
|
|
||||||
fn format_bytes(bytes: i64) -> String {
|
fn format_bytes(bytes: i64) -> String {
|
||||||
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
|
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||||
@@ -186,6 +187,9 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
let (success_msg, error_msg) = get_action_messages(&action);
|
let (success_msg, error_msg) = get_action_messages(&action);
|
||||||
let success_msg = success_msg.to_string();
|
let success_msg = success_msg.to_string();
|
||||||
let error_msg = error_msg.to_string();
|
let error_msg = error_msg.to_string();
|
||||||
|
|
||||||
|
// Capture notifications signal before async (use_context unavailable in spawn_local)
|
||||||
|
let notifications = store.notifications;
|
||||||
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let action_req = if action == "delete_with_data" {
|
let action_req = if action == "delete_with_data" {
|
||||||
@@ -210,20 +214,20 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
resp.status(),
|
resp.status(),
|
||||||
resp.status_text()
|
resp.status_text()
|
||||||
);
|
);
|
||||||
toast_error(error_msg);
|
show_toast_with_signal(notifications, NotificationLevel::Error, error_msg);
|
||||||
} else {
|
} else {
|
||||||
logging::log!("Action {} executed successfully", action);
|
logging::log!("Action {} executed successfully", action);
|
||||||
toast_success(success_msg);
|
show_toast_with_signal(notifications, NotificationLevel::Success, success_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging::error!("Network error executing action: {}", e);
|
logging::error!("Network error executing action: {}", e);
|
||||||
toast_error(format!("{}: Bağlantı hatası", error_msg));
|
show_toast_with_signal(notifications, NotificationLevel::Error, format!("{}: Bağlantı hatası", error_msg));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging::error!("Failed to serialize request: {}", e);
|
logging::error!("Failed to serialize request: {}", e);
|
||||||
toast_error(error_msg);
|
show_toast_with_signal(notifications, NotificationLevel::Error, error_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,46 +13,57 @@ pub struct NotificationItem {
|
|||||||
// Toast Helper Functions (Clean Code: Single Responsibility)
|
// Toast Helper Functions (Clean Code: Single Responsibility)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Shows a toast notification using a direct signal reference.
|
||||||
|
/// Use this version inside async blocks (spawn_local) where use_context is unavailable.
|
||||||
|
/// Auto-removes after 5 seconds.
|
||||||
|
pub fn show_toast_with_signal(
|
||||||
|
notifications: RwSignal<Vec<NotificationItem>>,
|
||||||
|
level: NotificationLevel,
|
||||||
|
message: impl Into<String>,
|
||||||
|
) {
|
||||||
|
let id = js_sys::Date::now() as u64;
|
||||||
|
let notification = SystemNotification {
|
||||||
|
level,
|
||||||
|
message: message.into(),
|
||||||
|
};
|
||||||
|
let item = NotificationItem { id, notification };
|
||||||
|
|
||||||
|
notifications.update(|list| list.push(item));
|
||||||
|
|
||||||
|
// Auto-remove after 5 seconds
|
||||||
|
let _ = set_timeout(
|
||||||
|
move || {
|
||||||
|
notifications.update(|list| list.retain(|i| i.id != id));
|
||||||
|
},
|
||||||
|
std::time::Duration::from_secs(5),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows a toast notification with the given level and message.
|
/// Shows a toast notification with the given level and message.
|
||||||
|
/// Only works within reactive scope (components, effects). For async, use show_toast_with_signal.
|
||||||
/// Auto-removes after 5 seconds.
|
/// Auto-removes after 5 seconds.
|
||||||
pub fn show_toast(level: NotificationLevel, message: impl Into<String>) {
|
pub fn show_toast(level: NotificationLevel, message: impl Into<String>) {
|
||||||
if let Some(store) = use_context::<TorrentStore>() {
|
if let Some(store) = use_context::<TorrentStore>() {
|
||||||
let id = js_sys::Date::now() as u64;
|
show_toast_with_signal(store.notifications, level, message);
|
||||||
let notification = SystemNotification {
|
|
||||||
level,
|
|
||||||
message: message.into(),
|
|
||||||
};
|
|
||||||
let item = NotificationItem { id, notification };
|
|
||||||
|
|
||||||
store.notifications.update(|list| list.push(item));
|
|
||||||
|
|
||||||
// Auto-remove after 5 seconds
|
|
||||||
let notifications = store.notifications;
|
|
||||||
let _ = set_timeout(
|
|
||||||
move || {
|
|
||||||
notifications.update(|list| list.retain(|i| i.id != id));
|
|
||||||
},
|
|
||||||
std::time::Duration::from_secs(5),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function for success toasts
|
/// Convenience function for success toasts (reactive scope only)
|
||||||
pub fn toast_success(message: impl Into<String>) {
|
pub fn toast_success(message: impl Into<String>) {
|
||||||
show_toast(NotificationLevel::Success, message);
|
show_toast(NotificationLevel::Success, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function for error toasts
|
/// Convenience function for error toasts (reactive scope only)
|
||||||
pub fn toast_error(message: impl Into<String>) {
|
pub fn toast_error(message: impl Into<String>) {
|
||||||
show_toast(NotificationLevel::Error, message);
|
show_toast(NotificationLevel::Error, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function for info toasts
|
/// Convenience function for info toasts (reactive scope only)
|
||||||
pub fn toast_info(message: impl Into<String>) {
|
pub fn toast_info(message: impl Into<String>) {
|
||||||
show_toast(NotificationLevel::Info, message);
|
show_toast(NotificationLevel::Info, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function for warning toasts
|
/// Convenience function for warning toasts (reactive scope only)
|
||||||
pub fn toast_warning(message: impl Into<String>) {
|
pub fn toast_warning(message: impl Into<String>) {
|
||||||
show_toast(NotificationLevel::Warning, message);
|
show_toast(NotificationLevel::Warning, message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user