Files
email-organizer/app/templates/base.html

102 lines
3.8 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-theme="cupcake">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Email Organizer - Prototype{% endblock %}</title>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script src="https://unpkg.com/htmx-ext-loading-states@2.0.0"></script>
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></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" />
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.shake {
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
.fade-in-htmx.htmx-added {
opacity: 0;
}
.htmx-added .fade-in-htmx {
opacity: 0;
}
.fade-in-htmx {
opacity: 1;
transition: opacity 300ms ease-out;
}
/* Fade out transition for HTMX */
.fade-out-htmx.htmx-swapping {
opacity: 0;
transition: opacity 280ms ease-out;
}
/* Fade out transition for HTMX */
.htmx-swapping .fade-out-htmx {
opacity: 0;
transition: opacity 280ms ease-out;
}
</style>
{% block head %}{% endblock %}
</head>
<body class="min-h-screen flex flex-col" x-data="{}" x-on:open-modal.window="$refs.modal.showModal()"
x-on:close-modal.window="$refs.modal.close()"
hx-ext="loading-states"
data-loading-delay="200">
{% block header %}{% endblock %}
{% block content %}{% endblock %}
{% block modal %}{% endblock %}
</script>
<!-- Toast for 5xx errors -->
<div id="error-toast" class="toast toast-top toast-end hidden">
<div class="alert alert-error">
<span id="error-message"></span>
<button class="btn btn-sm btn-ghost" onclick="document.getElementById('error-toast').classList.add('hidden')">Close</button>
</div>
</div>
<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
document.addEventListener('htmx:configRequest', function (evt) {
if (typeof window.simulate500 === 'boolean' && window.simulate500) {
evt.detail.headers['X-Simulate-500'] = 'true';
}
});
</script>
</body>
</html>