Adds messages when stuff goes wrong

This commit is contained in:
2026-03-19 12:21:58 +01:00
parent a373b60750
commit dbafa6c99c
4 changed files with 156 additions and 29 deletions

View File

@@ -0,0 +1,118 @@
<script setup>
import { ref, watch, onUnmounted } from 'vue'
import { usePage } from '@inertiajs/vue3'
import { CheckCircleIcon, ExclamationTriangleIcon, XMarkIcon } from '@heroicons/vue/20/solid'
const page = usePage()
const visible = ref(false)
const message = ref('')
const type = ref('success') // 'success' | 'error'
let autoDismissTimer = null
function clearTimer() {
if (autoDismissTimer) {
clearTimeout(autoDismissTimer)
autoDismissTimer = null
}
}
function dismiss() {
visible.value = false
clearTimer()
}
function showFlash(flash) {
clearTimer()
if (flash?.error) {
message.value = flash.error
type.value = 'error'
visible.value = true
// Error messages stay until manually dismissed
} else if (flash?.success) {
message.value = flash.success
type.value = 'success'
visible.value = true
// Auto-dismiss success after 8 seconds
autoDismissTimer = setTimeout(() => {
visible.value = false
}, 8000)
} else {
visible.value = false
}
}
// Watch for flash prop changes on each Inertia page visit
watch(
() => page.props.flash,
(flash) => showFlash(flash),
{ immediate: true, deep: true }
)
onUnmounted(() => {
clearTimer()
})
</script>
<template>
<Transition
enter-active-class="transition duration-300 ease-out"
enter-from-class="-translate-y-full opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-200 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="-translate-y-full opacity-0"
>
<div
v-if="visible"
:class="[
'relative z-40 w-full shadow-lg',
type === 'error'
? 'bg-rose-600'
: 'bg-emerald-600',
]"
role="alert"
:aria-live="type === 'error' ? 'assertive' : 'polite'"
>
<div class="px-6 py-3 flex items-center gap-3 max-w-7xl mx-auto">
<!-- Icon -->
<component
:is="type === 'error' ? ExclamationTriangleIcon : CheckCircleIcon"
class="w-5 h-5 text-white/90 shrink-0"
aria-hidden="true"
/>
<!-- Message -->
<p class="flex-1 text-sm font-medium text-white leading-snug">
{{ message }}
</p>
<!-- Dismiss button -->
<button
type="button"
class="ml-auto shrink-0 rounded p-1 text-white/70 hover:text-white hover:bg-white/10 transition-colors focus:outline-none focus:ring-2 focus:ring-white/40"
aria-label="Dismiss notification"
@click="dismiss"
>
<XMarkIcon class="w-4 h-4" />
</button>
</div>
<!-- Progress bar for success auto-dismiss -->
<div
v-if="type === 'success'"
class="absolute bottom-0 left-0 h-[2px] bg-white/30 animate-[shrink_8s_linear_forwards]"
style="width: 100%"
/>
</div>
</Transition>
</template>
<style scoped>
@keyframes shrink {
from { width: 100%; }
to { width: 0%; }
}
</style>