Use toast notifications over JavaScript alerts (#37)

Implement UI toast notifications
This commit is contained in:
Elias Benbourenane
2025-01-31 19:31:59 -05:00
committed by GitHub
parent 99b4a3152d
commit 1b9b7e203e
5 changed files with 67 additions and 12 deletions

View File

@@ -268,9 +268,9 @@
if (!response.ok) throw new Error(await response.text());
alert('Configuration saved successfully!');
createToast('Configuration saved successfully!');
} catch (error) {
alert(`Error saving configuration: ${error.message}`);
createToast(`Error saving configuration: ${error.message}`, 'error');
}
});

View File

@@ -68,11 +68,11 @@
.filter(url => url.length > 0);
if (urls.length === 0) {
alert('Please submit at least one torrent');
createToast('Please submit at least one torrent', 'warning');
return;
}
if (urls.length >= 100) {
alert('Please submit less than 100 torrents at a time');
createToast('Please submit less than 100 torrents at a time', 'warning');
return;
}
@@ -92,12 +92,16 @@
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Unknown error');
if (result.errors && result.errors.length > 0) {
alert(`Added ${result.results.length} torrents with ${result.errors.length} errors:\n${result.errors.join('\n')}`);
if (result.results.length > 0) {
createToast(`Added ${result.results.length} torrents with ${result.errors.length} errors:\n${result.errors.join('\n')}`, 'warning');
} else {
createToast(`Failed to add torrents:\n${result.errors.join('\n')}`, 'error');
}
} else {
alert(`Successfully added ${result.results.length} torrents!`);
createToast(`Successfully added ${result.results.length} torrents!`);
}
} catch (error) {
alert(`Error adding downloads: ${error.message}`);
createToast(`Error adding downloads: ${error.message}`, 'error');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;

View File

@@ -170,9 +170,10 @@
method: 'DELETE'
});
await loadTorrents();
createToast('Torrent deleted successfully');
} catch (error) {
console.error('Error deleting torrent:', error);
alert('Failed to delete torrent');
createToast('Failed to delete torrent', 'error');
}
}
@@ -185,9 +186,10 @@
);
await Promise.all(deletePromises);
await loadTorrents();
createToast('Selected torrents deleted successfully');
} catch (error) {
console.error('Error deleting torrents:', error);
alert('Failed to delete some torrents');
createToast('Failed to delete some torrents' , 'error');
}
}

View File

@@ -57,6 +57,9 @@
</style>
</head>
<body>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<!-- Toast messages will be created dynamically here -->
</div>
<nav class="navbar navbar-expand-lg navbar-light mb-4">
<div class="container">
<a class="navbar-brand" href="/">
@@ -115,6 +118,52 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
/**
* Create a toast message
* @param {string} message - The message to display
* @param {string} [type='success'] - The type of toast (success, warning, error)
*/
const createToast = (message, type = 'success') => {
type = ['success', 'warning', 'error'].includes(type) ? type : 'success';
const toastTimeouts = {
success: 5000,
warning: 10000,
error: 15000
}
const toastContainer = document.querySelector('.toast-container');
const toastId = `toast-${Date.now()}`;
const toastHtml = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header ${type === 'error' ? 'bg-danger text-white' : type === 'warning' ? 'bg-warning text-dark' : 'bg-success text-white'}">
<strong class="me-auto">
${type === 'error' ? 'Error' : type === 'warning' ? 'Warning' : 'Success'}
</strong>
<button type="button" class="btn-close ${type === 'warning' ? '' : 'btn-close-white'}" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${message.replace(/\n/g, '<br>')}
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: toastTimeouts[type]
});
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
};
document.addEventListener('DOMContentLoaded', function() {
fetch('/internal/version')
.then(response => response.json())

View File

@@ -62,7 +62,7 @@
let mediaIds = document.getElementById('mediaIds').value.split(',').map(id => id.trim());
let arr = document.getElementById('arrSelect').value;
if (!arr) {
alert('Please select an Arr instance');
createToast('Please select an Arr instance', 'warning');
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
return;
@@ -81,9 +81,9 @@
});
if (!response.ok) throw new Error(await response.text());
alert('Repair process initiated successfully!');
createToast('Repair process initiated successfully!');
} catch (error) {
alert(`Error starting repair: ${error.message}`);
createToast(`Error starting repair: ${error.message}`, 'error');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;