- {move || notifications.get().into_iter().map(|item| {
- let alert_class = match item.notification.level {
- NotificationLevel::Info => "alert-info",
- NotificationLevel::Success => "alert-success",
- NotificationLevel::Warning => "alert-warning",
- NotificationLevel::Error => "alert-error",
- };
-
- let icon = match item.notification.level {
- NotificationLevel::Info => view! {
},
- NotificationLevel::Success => view! {
},
- NotificationLevel::Warning => view! {
},
- NotificationLevel::Error => view! {
},
- };
-
- view! {
-
- {icon}
- {item.notification.message}
-
- }
- }).collect::
>()}
+
+ {get_toast_icon(&level)}
+ {message}
+
+ }
+}
+
+/// Main toast container - renders all active notifications
+#[component]
+pub fn ToastContainer() -> impl IntoView {
+ let store = use_context::().expect("TorrentStore not provided");
+ let notifications = store.notifications;
+
+ view! {
+
+
+ }
+ }
+ />
}
}
diff --git a/frontend/src/components/torrent/add_torrent.rs b/frontend/src/components/torrent/add_torrent.rs
index 2f51cca..54bb278 100644
--- a/frontend/src/components/torrent/add_torrent.rs
+++ b/frontend/src/components/torrent/add_torrent.rs
@@ -1,5 +1,6 @@
use leptos::*;
use leptos::html::Dialog;
+use crate::store::{toast_success, toast_error, toast_warning};
#[component]
@@ -22,6 +23,7 @@ pub fn AddTorrentModal(
let handle_submit = move |_| {
let uri_val = uri.get();
if uri_val.is_empty() {
+ toast_warning("Lütfen bir Magnet URI veya URL girin");
set_error_msg.set(Some("Please enter a Magnet URI or URL".to_string()));
return;
}
@@ -42,6 +44,7 @@ pub fn AddTorrentModal(
Ok(resp) => {
if resp.ok() {
logging::log!("Torrent added successfully");
+ toast_success("Torrent eklendi");
set_loading.set(false);
if let Some(dialog) = dialog_ref.get() {
dialog.close();
@@ -51,12 +54,14 @@ pub fn AddTorrentModal(
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
logging::error!("Failed to add torrent: {} - {}", status, text);
+ toast_error("Torrent eklenemedi");
set_error_msg.set(Some(format!("Error {}: {}", status, text)));
set_loading.set(false);
}
}
Err(e) => {
logging::error!("Network error: {}", e);
+ toast_error("Bağlantı hatası");
set_error_msg.set(Some(format!("Network Error: {}", e)));
set_loading.set(false);
}
@@ -64,6 +69,7 @@ pub fn AddTorrentModal(
}
Err(e) => {
logging::error!("Serialization error: {}", e);
+ toast_error("İstek hatası");
set_error_msg.set(Some(format!("Request Error: {}", e)));
set_loading.set(false);
}
diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs
index 6e50987..4424f43 100644
--- a/frontend/src/components/torrent/table.rs
+++ b/frontend/src/components/torrent/table.rs
@@ -1,6 +1,7 @@
use leptos::*;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
+use crate::store::{get_action_messages, toast_success, toast_error};
fn format_bytes(bytes: i64) -> String {
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
@@ -181,6 +182,11 @@ pub fn TorrentTable() -> impl IntoView {
logging::log!("TorrentTable Action: {} on {}", action, hash);
set_menu_visible.set(false); // Close menu immediately
+ // Get action messages for toast (Clean Code: DRY)
+ let (success_msg, error_msg) = get_action_messages(&action);
+ let success_msg = success_msg.to_string();
+ let error_msg = error_msg.to_string();
+
spawn_local(async move {
let action_req = if action == "delete_with_data" {
"delete_with_data"
@@ -204,13 +210,21 @@ pub fn TorrentTable() -> impl IntoView {
resp.status(),
resp.status_text()
);
+ toast_error(error_msg);
} else {
logging::log!("Action {} executed successfully", action);
+ toast_success(success_msg);
}
}
- Err(e) => logging::error!("Network error executing action: {}", e),
+ Err(e) => {
+ logging::error!("Network error executing action: {}", e);
+ toast_error(format!("{}: Bağlantı hatası", error_msg));
+ }
},
- Err(e) => logging::error!("Failed to serialize request: {}", e),
+ Err(e) => {
+ logging::error!("Failed to serialize request: {}", e);
+ toast_error(error_msg);
+ }
}
});
};
diff --git a/frontend/src/store.rs b/frontend/src/store.rs
index a51abcf..3045f08 100644
--- a/frontend/src/store.rs
+++ b/frontend/src/store.rs
@@ -1,7 +1,7 @@
use futures::StreamExt;
use gloo_net::eventsource::futures::EventSource;
use leptos::*;
-use shared::{AppEvent, GlobalStats, SystemNotification, Torrent};
+use shared::{AppEvent, GlobalStats, NotificationLevel, SystemNotification, Torrent};
#[derive(Clone, Debug, PartialEq)]
pub struct NotificationItem {
@@ -9,6 +9,72 @@ pub struct NotificationItem {
pub notification: SystemNotification,
}
+// ============================================================================
+// Toast Helper Functions (Clean Code: Single Responsibility)
+// ============================================================================
+
+/// Shows a toast notification with the given level and message.
+/// Auto-removes after 5 seconds.
+pub fn show_toast(level: NotificationLevel, message: impl Into) {
+ if let Some(store) = use_context::() {
+ let id = js_sys::Date::now() as u64;
+ 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
+pub fn toast_success(message: impl Into) {
+ show_toast(NotificationLevel::Success, message);
+}
+
+/// Convenience function for error toasts
+pub fn toast_error(message: impl Into) {
+ show_toast(NotificationLevel::Error, message);
+}
+
+/// Convenience function for info toasts
+pub fn toast_info(message: impl Into) {
+ show_toast(NotificationLevel::Info, message);
+}
+
+/// Convenience function for warning toasts
+pub fn toast_warning(message: impl Into) {
+ show_toast(NotificationLevel::Warning, message);
+}
+
+// ============================================================================
+// Action Message Mapping (Clean Code: DRY Principle)
+// ============================================================================
+
+/// Maps torrent action strings to user-friendly Turkish messages.
+/// Returns (success_message, error_message)
+pub fn get_action_messages(action: &str) -> (&'static str, &'static str) {
+ match action {
+ "start" => ("Torrent başlatıldı", "Torrent başlatılamadı"),
+ "stop" => ("Torrent durduruldu", "Torrent durdurulamadı"),
+ "pause" => ("Torrent duraklatıldı", "Torrent duraklatılamadı"),
+ "delete" => ("Torrent silindi", "Torrent silinemedi"),
+ "delete_with_data" => ("Torrent ve verileri silindi", "Torrent silinemedi"),
+ "recheck" => ("Torrent kontrol ediliyor", "Kontrol başlatılamadı"),
+ _ => ("İşlem tamamlandı", "İşlem başarısız"),
+ }
+}
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FilterStatus {
All,