- hooks and bulk, record, toolbar and widget actions may return it - 403 forbidden with the localized message and field details; the write's transaction is rolled back; other errors stay the opaque 500 - form shows a refused save as a persistent banner and keeps the values; a refused delete is a toast - smoke tests, OpenAPI notes, dist, README, docs
53 lines
1.9 KiB
Vue
53 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { CircleAlert } from '@lucide/vue'
|
|
import { t, tc } from '../../app/i18n'
|
|
|
|
// 422 banner (design screen 4): "Nie udało się zapisać. Popraw N pola…".
|
|
// Messages of keys that are not form fields are listed here, so no server
|
|
// message is lost. With `forbidden` (UI-SPEC S6: a save the server refused
|
|
// with 403) the same geometry shows that text instead; it stays until the
|
|
// next save attempt, and field messages still render on their fields.
|
|
const props = withDefaults(
|
|
defineProps<{ errors: Record<string, string[]>; fieldNames: string[]; forbidden?: string | null }>(),
|
|
{ forbidden: null },
|
|
)
|
|
|
|
const count = computed(() => Object.keys(props.errors).length)
|
|
const orphans = computed(() =>
|
|
Object.entries(props.errors)
|
|
.filter(([name]) => !props.fieldNames.includes(name))
|
|
.flatMap(([, messages]) => messages),
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="forbidden"
|
|
role="alert"
|
|
data-forbidden-banner
|
|
class="flex items-start gap-3 rounded-inner bg-danger-soft px-[18px] py-3.5 text-danger"
|
|
>
|
|
<CircleAlert :size="20" class="mt-px shrink-0" aria-hidden="true" />
|
|
<div class="flex min-w-0 flex-col gap-1">
|
|
<p class="[overflow-wrap:anywhere]">{{ forbidden }}</p>
|
|
<p v-for="(text, index) in orphans" :key="index" class="text-[13px] [overflow-wrap:anywhere]">{{ text }}</p>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else-if="count > 0"
|
|
role="alert"
|
|
data-error-banner
|
|
class="flex items-start gap-3 rounded-inner bg-danger-soft px-[18px] py-3.5 text-danger"
|
|
>
|
|
<CircleAlert :size="20" class="mt-px shrink-0" aria-hidden="true" />
|
|
<div class="flex flex-col gap-1">
|
|
<p>
|
|
<strong>{{ t('backend::lang.form.error_title') }}</strong>
|
|
{{ tc('backend::lang.form.error_fields', count) }}
|
|
</p>
|
|
<p v-for="(text, index) in orphans" :key="index" class="text-[13px]">{{ text }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|