Files
summercms/admin/src/state/useToasts.ts
Jakub Zych 126ca5b8ed feat(10-03): open, edit and save a record with toast and 422 feedback
- The SPA loads the backend::lang bundle before /auth/me, sets the
  document language from meta.locale and renders plural messages with
  Intl.PluralRules; interpolate mirrors phrasebook for :name/:Name/:NAME
- Create and record routes; mapWinterUrl maps recordUrl and redirects
  onto the controller's list, create and record routes only
- List rows open their record; FormView loads the form schema and the
  record, shows context-allowed fields in the span grid, saves values
  keyed by field name and toasts the resolved saved message
- A 422 puts each message under its field (aria-invalid,
  aria-describedby), shows the plural banner, focuses the first invalid
  field in schema order and clears a field's error on change
- The D-05 registry maps text, textarea, number and dropdown; any other
  type renders the unsupported-field box with the type in DM Mono
- The admin OpenAPI document declares the write request bodies
  (AdminRecord, AdminIDsRequest) and the list filter query as a
  deepObject, so the typed client can send them
- New backend::lang form.load_failed key; boardwalk/dist rebuilt
2026-09-27 16:43:47 +02:00

53 lines
1.3 KiB
TypeScript

// Global toast queue (design: State Management). Toasts outlive route
// changes because AppShell mounts the renderer, so "Zapisz i zamknij" can
// show its toast on the list it returns to.
import { readonly, ref } from 'vue'
export type ToastTone = 'success' | 'danger'
export interface ToastItem {
id: number
text: string
tone: ToastTone
}
/** Auto-dismiss delay in milliseconds (design: about 4 s). */
export const TOAST_TIMEOUT = 4000
const items = ref<ToastItem[]>([])
const timers = new Map<number, ReturnType<typeof setTimeout>>()
let nextId = 1
export const toasts = readonly(items)
export function showToast(text: string, tone: ToastTone = 'success'): number {
const id = nextId++
items.value = [...items.value, { id, text, tone }]
timers.set(
id,
setTimeout(() => dismissToast(id), TOAST_TIMEOUT),
)
return id
}
export function dismissToast(id: number): void {
const timer = timers.get(id)
if (timer !== undefined) {
clearTimeout(timer)
timers.delete(id)
}
items.value = items.value.filter((item) => item.id !== id)
}
export function clearToasts(): void {
for (const timer of timers.values()) {
clearTimeout(timer)
}
timers.clear()
items.value = []
}
export function useToasts() {
return { toasts, showToast, dismissToast, clearToasts }
}