diff --git a/web/src/admin.ts b/web/src/admin.ts index d9754ef..65a0f6f 100644 --- a/web/src/admin.ts +++ b/web/src/admin.ts @@ -1,3 +1,5 @@ +import { showToast } from "./toast"; + async function triggerLibraryScan(): Promise { const token = localStorage.getItem("token"); if (!token) return; @@ -9,20 +11,14 @@ async function triggerLibraryScan(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Library scan started"); - } + showToast("Library scan started", "success"); } else { const error = await response.json(); - if ((window as any).showToast?.error) { - (window as any).showToast.error(error.error || "Failed to start scan"); - } + showToast(error.error || "Failed to start scan", "error"); } } catch (error) { console.error("Scan error:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to start library scan"); - } + showToast("Failed to start library scan", "error"); } } @@ -37,22 +33,14 @@ async function triggerQuickScan(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Quick scan started"); - } + showToast("Quick scan started", "success"); } else { const error = await response.json(); - if ((window as any).showToast?.error) { - (window as any).showToast.error( - error.error || "Failed to start quick scan", - ); - } + showToast(error.error || "Failed to start quick scan", "error"); } } catch (error) { console.error("Quick scan error:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to start quick scan"); - } + showToast("Failed to start quick scan", "error"); } } @@ -116,11 +104,7 @@ async function scanAllLibraries(): Promise { const libsData = await libsResp.json(); if (!libsData.data || libsData.data.length === 0) { - if ((window as any).showToast?.error) { - (window as any).showToast.error( - "No libraries found. Please create a library first.", - ); - } + showToast("No libraries found. Please create a library first.", "error"); return; } @@ -149,20 +133,14 @@ async function scanAllLibraries(): Promise { } if (jobs.length === 0) { - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to start scan for any library"); - } + showToast("Failed to start scan for any library", "error"); return; } showScanProgress(jobs, libraryNames); } catch (error) { console.error("Scan error:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error( - "Failed to start scan: " + (error as Error).message, - ); - } + showToast("Failed to start scan: " + (error as Error).message, "error"); } } diff --git a/web/src/analytics.ts b/web/src/analytics.ts index 4932061..3fcd8d4 100644 --- a/web/src/analytics.ts +++ b/web/src/analytics.ts @@ -1,3 +1,5 @@ +import { showToast } from "./toast"; + async function loadAnalytics(): Promise { const token = localStorage.getItem("token"); if (!token) return; @@ -31,9 +33,7 @@ async function loadAnalytics(): Promise { } } catch (error) { console.error("Failed to load analytics:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to load analytics data"); - } + showToast("Failed to load analytics data", "error"); } } @@ -123,4 +123,4 @@ function renderPopularBooks(popular: PopularBooksResponse): void { document.addEventListener("DOMContentLoaded", loadAnalytics); -(window as any).loadAnalytics = loadAnalytics; +export { loadAnalytics }; diff --git a/web/src/conflicts.ts b/web/src/conflicts.ts index e532860..d3b323c 100644 --- a/web/src/conflicts.ts +++ b/web/src/conflicts.ts @@ -1,3 +1,5 @@ +import { showToast } from "./toast"; + async function refreshConflicts(): Promise { const token = localStorage.getItem("token"); if (!token) return; @@ -36,23 +38,15 @@ async function resolveConflict( }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Conflict resolved"); - } + showToast("Conflict resolved", "success"); refreshConflicts(); } else { const error = await response.json(); - if ((window as any).showToast?.error) { - (window as any).showToast.error( - error.error || "Failed to resolve conflict", - ); - } + showToast(error.error || "Failed to resolve conflict", "error"); } } catch (error) { console.error("Failed to resolve conflict:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to resolve conflict"); - } + showToast("Failed to resolve conflict", "error"); } } @@ -75,16 +69,12 @@ async function bulkResolve( if (response.ok) { const data: BulkResolveResponse = await response.json(); - if ((window as any).showToast?.success) { - (window as any).showToast.success(`Resolved ${data.success} conflicts`); - } + showToast(`Resolved ${data.success} conflicts`, "success"); refreshConflicts(); } } catch (error) { console.error("Failed to bulk resolve:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to bulk resolve conflicts"); - } + showToast("Failed to bulk resolve conflicts", "error"); } } @@ -103,16 +93,12 @@ async function bulkDismiss(conflictIds: string[]): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Conflicts dismissed"); - } + showToast("Conflicts dismissed", "success"); refreshConflicts(); } } catch (error) { console.error("Failed to dismiss conflicts:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to dismiss conflicts"); - } + showToast("Failed to dismiss conflicts", "error"); } } @@ -127,16 +113,12 @@ async function dismissAllResolved(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Resolved conflicts dismissed"); - } + showToast("Resolved conflicts dismissed", "success"); refreshConflicts(); } } catch (error) { console.error("Failed to dismiss resolved:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to dismiss resolved conflicts"); - } + showToast("Failed to dismiss resolved conflicts", "error"); } } @@ -214,9 +196,7 @@ function handleResolveSubmit(event: Event): void { )?.value; if (!conflictId || !winner) { - if ((window as any).showToast?.error) { - (window as any).showToast.error("Please select a winner"); - } + showToast("Please select a winner", "error"); return; } diff --git a/web/src/custom-section-builder.ts b/web/src/custom-section-builder.ts index 37e7705..6601ad7 100644 --- a/web/src/custom-section-builder.ts +++ b/web/src/custom-section-builder.ts @@ -1,3 +1,6 @@ +import { apiPost } from "./api"; +import { showToast } from "./toast"; + interface FilterField { id: string; label: string; @@ -395,7 +398,7 @@ async function searchBooks(): Promise { displaySearchResults(data.books || []); } catch (error) { console.error("Search books error:", error); - (window as any).showToast?.error("Failed to search books"); + showToast("Failed to search books", "error"); } } @@ -432,13 +435,13 @@ function displaySearchResults(books: BookInfo[]): void { resultsContainer.classList.remove("hidden"); } -(window as any).addBookToSelection = function ( +const addBookToSelection = ( bookId: string, title: string, author: string, -): void { +): void => { if (selectedBooks.has(bookId)) { - (window as any).showToast?.warning("Book already selected"); + showToast("Book already selected", "error"); return; } @@ -452,7 +455,7 @@ function displaySearchResults(books: BookInfo[]): void { updateSelectedBooksDisplay(); }; -(window as any).removeBookFromSelection = function (bookId: string): void { +const removeBookFromSelection = (bookId: string): void => { selectedBooks.delete(bookId); updateSelectedBooksDisplay(); }; @@ -491,7 +494,7 @@ async function loadPreview(): Promise { const libraryId = librarySelect?.value; if (!libraryId) { - (window as any).showToast?.error("Please select a library first"); + showToast("Please select a library first", "error"); return; } @@ -502,7 +505,7 @@ async function loadPreview(): Promise { '
'; try { - const response = await (window as any).api.post("/collections/preview", { + const response = await apiPost("/collections/preview", { library_id: libraryId, rules: rules, manual_book_ids: manualBookIds, @@ -604,7 +607,7 @@ async function saveCustomSection(event: Event): Promise { .value; if (!libraryId || !name) { - (window as any).showToast?.error("Please fill in required fields"); + showToast("Please fill in required fields", "error"); return; } @@ -612,12 +615,12 @@ async function saveCustomSection(event: Event): Promise { const manualBookIds = Array.from(selectedBooks.keys()); if (rules.length === 0 && manualBookIds.length === 0) { - (window as any).showToast?.error("Please add filter rules or select books"); + showToast("Please add filter rules or select books", "error"); return; } try { - const response = await (window as any).api.post("/collections", { + const response = await apiPost("/collections", { library_id: libraryId, name: name, icon: icon, @@ -629,7 +632,7 @@ async function saveCustomSection(event: Event): Promise { }); if (response.ok) { - (window as any).showToast?.success("Custom section created successfully"); + showToast("Custom section created successfully", "success"); setTimeout(() => { window.location.href = "/dashboard"; }, 1000); @@ -638,7 +641,7 @@ async function saveCustomSection(event: Event): Promise { } } catch (error) { console.error("Save custom section error:", error); - (window as any).showToast?.error("Failed to save custom section"); + showToast("Failed to save custom section", "error"); } } @@ -649,3 +652,5 @@ function builderEscapeHtml(text: string): string { } document.addEventListener("DOMContentLoaded", initCustomSectionBuilder); + +export { addBookToSelection, removeBookFromSelection }; diff --git a/web/src/dashboard.ts b/web/src/dashboard.ts index 33b3e91..d7e8249 100644 --- a/web/src/dashboard.ts +++ b/web/src/dashboard.ts @@ -1,3 +1,6 @@ +import { apiPost, apiPut } from "./api"; +import { showToast } from "./toast"; + // Dashboard functionality with unified collections architecture // Procedural/imperative style (no OOP) @@ -19,7 +22,7 @@ async function openDashboardSettings(): Promise { ) as HTMLSelectElement; const libraryId = librarySelect?.value; if (!libraryId) { - (window as any).showToast.error("No library selected"); + showToast("No library selected", "error"); return; } @@ -34,7 +37,7 @@ async function openDashboardSettings(): Promise { const prefs = await response.json(); applyPreferencesToModal(prefs); } else { - (window as any).showToast.error("Failed to load library preferences"); + showToast("Failed to load library preferences", "error"); console.error("API Error:", response.status, response.statusText); return; // Don't open modal with stale data. } @@ -125,20 +128,18 @@ async function saveDashboardSettings(): Promise { "library-select", ) as HTMLSelectElement; const libraryId = librarySelect?.value || ""; - const response = await (window as any).api.put("/dashboard/preferences", { + const response = await apiPut("/dashboard/preferences", { library_id: libraryId, hidden_collections: hiddenCollections, collection_order: collectionOrder, items_per_section: parseInt(itemsPerCollection), }); if (response.ok) { - (window as any).showToast.success("Dashboard settings saved"); + showToast("Dashboard settings saved", "success"); closeDashboardSettings(); // Fetch updated sections and re-render (like switchLibrary does) if (!libraryId) { - (window as any).showToast.error( - "Unable to refresh dashboard - no library selected", - ); + showToast("Unable to refresh dashboard - no library selected", "error"); return; } const sectionResponse = await fetch( @@ -155,11 +156,11 @@ async function saveDashboardSettings(): Promise { renderDashboardCollections(data.sections); localStorage.setItem("selectedLibraryId", libraryId); } else { - (window as any).showToast.error("Failed to refresh sections"); + showToast("Failed to refresh sections", "error"); } } } catch (error) { - (window as any).showToast.error("Failed to save settings"); + showToast("Failed to save settings", "error"); console.error("Save dashboard settings error:", error); } } @@ -176,21 +177,16 @@ async function restoreSystemCollection( } try { - const response = await (window as any).api.post( - "/dashboard/restore-system-collection", - { - collection_name: collectionName, - }, - ); + const response = await apiPost("/dashboard/restore-system-collection", { + collection_name: collectionName, + }); if (response.ok) { - (window as any).showToast.success( - `"${collectionTitle}" restored to defaults`, - ); + showToast(`"${collectionTitle}" restored to defaults`, "success"); setTimeout(() => window.location.reload(), 1000); } } catch (error) { - (window as any).showToast.error("Failed to restore system collection"); + showToast("Failed to restore system collection", "error"); console.error("Restore system collection error:", error); } } @@ -232,7 +228,7 @@ async function switchLibrary(libraryId: string): Promise { renderDashboardCollections(data.sections); localStorage.setItem("selectedLibrary", libraryId); } catch (error) { - (window as any).showToast.error("Failed to load library"); + showToast("Failed to load library", "error"); console.error("Switch library error:", error); } finally { loading.classList.add("hidden"); @@ -366,7 +362,7 @@ async function reloadPage(): Promise { const currentLibraryId = librarySelect?.value; if (!currentLibraryId) { - (window as any).showToast.error("No library selected"); + showToast("No library selected", "error"); return; } diff --git a/web/src/device-management.ts b/web/src/device-management.ts index 36bb61d..769d7c5 100644 --- a/web/src/device-management.ts +++ b/web/src/device-management.ts @@ -1,3 +1,5 @@ +import { Alpine } from "./alpine"; +import { showToast } from "./toast"; // Device Management - Token copy and regeneration // Procedural style with proper types (no OOP) @@ -29,17 +31,11 @@ function copyToClipboard(text: string, label: string): void { navigator.clipboard .writeText(text) .then(() => { - const toast = (window as any).showToast; - if (toast) { - toast.success(`${label} copied to clipboard`); - } + showToast(`${label} copied to clipboard`, "success"); }) .catch((err: unknown) => { console.error("Failed to copy:", err); - const toast = (window as any).showToast; - if (toast) { - toast.error("Failed to copy to clipboard"); - } + showToast("Failed to copy to clipboard", "error"); }); } @@ -75,21 +71,16 @@ function regenerateDeviceToken(deviceId: string, event: Event): void { return response.json() as Promise; }) .then((_data: RegenerateTokenResponse) => { - const toast = (window as any).showToast; - if (toast) { - toast.success( - "Token regenerated successfully - update your device config", - ); - } + showToast( + "Token regenerated successfully - update your device config", + "success", + ); // Reload page to show new token setTimeout(() => location.reload(), 1500); }) .catch((error: unknown) => { console.error("Error:", error); - const toast = (window as any).showToast; - if (toast) { - toast.error("Failed to regenerate token"); - } + showToast("Failed to regenerate token", "error"); if (btn) { btn.disabled = false; btn.innerHTML = originalText; @@ -98,5 +89,7 @@ function regenerateDeviceToken(deviceId: string, event: Event): void { } // Export functions for global access (called from template onclick attributes) -window.copyToClipboard = copyToClipboard; -window.regenerateDeviceToken = regenerateDeviceToken; +Alpine.global("devices", { + copyToClipboard, + regenerateDeviceToken, +}); diff --git a/web/src/linking.ts b/web/src/linking.ts index e08c78d..b899cc3 100644 --- a/web/src/linking.ts +++ b/web/src/linking.ts @@ -1,3 +1,5 @@ +import { showToast } from "./toast"; + async function loadUnlinkedBooks(): Promise { const token = localStorage.getItem("token"); if (!token) return; @@ -91,21 +93,15 @@ async function linkBook( }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Book linked successfully"); - } + showToast("Book linked successfully", "success"); loadUnlinkedBooks(); } else { const error = await response.json(); - if ((window as any).showToast?.error) { - (window as any).showToast.error(error.error || "Failed to link book"); - } + showToast(error.error || "Failed to link book", "error"); } } catch (error) { console.error("Failed to link book:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to link book"); - } + showToast("Failed to link book", "error"); } } @@ -127,18 +123,12 @@ async function autoLinkBooks(): Promise { if (response.ok) { const data = await response.json(); - if ((window as any).showToast?.success) { - (window as any).showToast.success( - `Auto-linked ${data.linked_count || 0} books`, - ); - } + showToast(`Auto-linked ${data.linked_count || 0} books`, "success"); loadUnlinkedBooks(); } } catch (error) { console.error("Failed to auto-link:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to auto-link books"); - } + showToast("Failed to auto-link books", "error"); } } diff --git a/web/src/queue.ts b/web/src/queue.ts index 8b02948..a80abee 100644 --- a/web/src/queue.ts +++ b/web/src/queue.ts @@ -1,3 +1,5 @@ +import { showToast } from "./toast"; + async function refreshQueue(): Promise { const token = localStorage.getItem("token"); if (!token) return; @@ -27,16 +29,12 @@ async function processPendingItems(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Processing queue items"); - } + showToast("Processing queue items", "success"); refreshQueue(); } } catch (error) { console.error("Failed to process queue:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to process queue"); - } + showToast("Failed to process queue", "error"); } } @@ -53,16 +51,12 @@ async function clearFailedItems(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Failed items cleared"); - } + showToast("Failed items cleared", "success"); refreshQueue(); } } catch (error) { console.error("Failed to clear failed items:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to clear items"); - } + showToast("Failed to clear items", "error"); } } @@ -79,16 +73,12 @@ async function clearAllItems(): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Queue cleared"); - } + showToast("Queue cleared", "success"); refreshQueue(); } } catch (error) { console.error("Failed to clear queue:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to clear queue"); - } + showToast("Failed to clear queue", "error"); } } @@ -103,16 +93,12 @@ async function retryQueueItem(itemId: string): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Item queued for retry"); - } + showToast("Item queued for retry", "success"); refreshQueue(); } } catch (error) { console.error("Failed to retry item:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to retry item"); - } + showToast("Failed to retry item", "error"); } } @@ -127,16 +113,12 @@ async function deleteQueueItem(itemId: string): Promise { }); if (response.ok) { - if ((window as any).showToast?.success) { - (window as any).showToast.success("Item deleted"); - } + showToast("Item deleted", "success"); refreshQueue(); } } catch (error) { console.error("Failed to delete item:", error); - if ((window as any).showToast?.error) { - (window as any).showToast.error("Failed to delete item"); - } + showToast("Failed to delete item", "error"); } }