feat: implement global error handling with Alpine.js and HTMX toast notifications
- Added Alpine.js store for managing error toast state - Implemented server error toast notifications for 5xx errors - Replaced manual JavaScript error handling with Alpine.js implementation - Updated body tag to use HTMX response error event listener - Improved error display with better styling and user experience This change provides a consistent way to show server errors to users across the application using HTMX and Alpine.js, making error handling more maintainable and reusable.
This commit is contained in:
@@ -13,6 +13,37 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" defer></script>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/dist/tippy.css" />
|
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/dist/tippy.css" />
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.store('errorToast', {
|
||||||
|
show: false,
|
||||||
|
message: '',
|
||||||
|
hide() {
|
||||||
|
this.show = false;
|
||||||
|
},
|
||||||
|
handleError(detail) {
|
||||||
|
|
||||||
|
if (detail.xhr.status >= 500 && detail.xhr.status < 600) {
|
||||||
|
// Extract error message from response
|
||||||
|
let errorMessage = 'Server Error';
|
||||||
|
try {
|
||||||
|
const responseJson = JSON.parse(detail.xhr.response);
|
||||||
|
if (responseJson.error) {
|
||||||
|
errorMessage = responseJson.error;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If not JSON, use the raw response
|
||||||
|
errorMessage = detail.xhr.response || 'Server Error';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set error message and show toast using Alpine store
|
||||||
|
this.message = errorMessage;
|
||||||
|
this.show = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@keyframes shake {
|
@keyframes shake {
|
||||||
0%, 100% { transform: translateX(0); }
|
0%, 100% { transform: translateX(0); }
|
||||||
@@ -48,51 +79,27 @@
|
|||||||
</style>
|
</style>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen flex flex-col" x-data="{}" x-on:open-modal.window="$refs.modal.showModal()"
|
<body
|
||||||
x-on:close-modal.window="$refs.modal.close()"
|
x-on:htmx:response-error.camel="$store.errorToast.handleError($event.detail)"
|
||||||
hx-ext="loading-states"
|
class="min-h-screen flex flex-col" x-data="{}" x-on:open-modal.window="$refs.modal.showModal()"
|
||||||
data-loading-delay="200">
|
x-on:close-modal.window="$refs.modal.close()" hx-ext="loading-states" data-loading-delay="200">
|
||||||
{% block header %}{% endblock %}
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
{% block modal %}{% endblock %}
|
{% block modal %}{% endblock %}
|
||||||
|
|
||||||
</script>
|
|
||||||
<!-- Toast for 5xx errors -->
|
<!-- Toast for 5xx errors -->
|
||||||
<div id="error-toast" class="toast toast-top toast-end w-full z-100 hidden">
|
<div id="error-toast" class="toast toast-top toast-end w-full z-100" x-show="$store.errorToast.show" x-transition.duration.200ms>
|
||||||
<div class="alert alert-error alert-lg">
|
<div class="alert alert-warning backdrop-blur-md bg-error/30 border border-error/20 relative">
|
||||||
<span id="error-message"></span>
|
|
||||||
<button class="btn btn-sm btn-ghost" onclick="document.getElementById('error-toast').classList.add('hidden')">
|
<span id="error-message" x-text="$store.errorToast.message"></span>
|
||||||
|
<button class="btn btn-sm btn-ghost absolute top-2 right-2" @click="$store.errorToast.hide()">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
<script>
|
|
||||||
// Configure HTMX to handle 5xx errors
|
|
||||||
document.addEventListener('htmx:responseError', function(evt) {
|
|
||||||
if (evt.detail.xhr.status >= 500 && evt.detail.xhr.status < 600) {
|
|
||||||
const toast = document.getElementById('error-toast');
|
|
||||||
const message = document.getElementById('error-message');
|
|
||||||
|
|
||||||
// Extract error message from response
|
|
||||||
let errorMessage = 'Server Error';
|
|
||||||
try {
|
|
||||||
const responseJson = JSON.parse(evt.detail.xhr.response);
|
|
||||||
if (responseJson.error) {
|
|
||||||
errorMessage = responseJson.error;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// If not JSON, use the raw response
|
|
||||||
errorMessage = evt.detail.xhr.response || 'Server Error';
|
|
||||||
}
|
|
||||||
|
|
||||||
message.textContent = errorMessage;
|
|
||||||
toast.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for global variable to set X-Simulate-500 header
|
// Check for global variable to set X-Simulate-500 header
|
||||||
document.addEventListener('htmx:configRequest', function (evt) {
|
document.addEventListener('htmx:configRequest', function (evt) {
|
||||||
if (typeof window.simulate500 === 'boolean' && window.simulate500) {
|
if (typeof window.simulate500 === 'boolean' && window.simulate500) {
|
||||||
|
|||||||
Reference in New Issue
Block a user