6c2a149f96
Introduce a global app banners store (GUI/src/stores/appBanners.ts) and switch authentication error/notification flows to use it. GUI/src/Layout.vue now consumes the banner store, replaces the single snackbar with a stacked, dismissible banner UI (styles, transitions and z-index included), and adds navigateToBrandTarget() to route the brand button based on auth state. GUI/src/routes/authentication/Login.vue was updated to push errors to the new banner store and remove the local alert. Minor route adjustments in GUI/src/plugins/routesLayout.ts: rename 'Dash' to 'Dashboard' and change Login visibility to Unauthenticated. codexInfo.md updated to document these UI/behavior changes.
83 lines
1.8 KiB
TypeScript
83 lines
1.8 KiB
TypeScript
import { ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
|
|
export type AppBannerType = 'success' | 'info' | 'warning' | 'error'
|
|
|
|
export interface AppBannerMessage {
|
|
id: string
|
|
type: AppBannerType
|
|
title?: string
|
|
message: string
|
|
createdAt: number
|
|
}
|
|
|
|
interface PushBannerInput {
|
|
type?: AppBannerType
|
|
title?: string
|
|
message: string
|
|
}
|
|
|
|
const MAX_BANNERS = 8
|
|
|
|
function createBannerId() {
|
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
return crypto.randomUUID()
|
|
}
|
|
|
|
return `banner-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
}
|
|
|
|
export const useAppBannersStore = defineStore('app-banners', () => {
|
|
const banners = ref<AppBannerMessage[]>([])
|
|
|
|
function push(input: PushBannerInput) {
|
|
const now = Date.now()
|
|
const type = input.type ?? 'info'
|
|
const normalizedMessage = input.message.trim()
|
|
if (normalizedMessage.length === 0) {
|
|
return ''
|
|
}
|
|
|
|
const lastBanner = banners.value[banners.value.length - 1]
|
|
if (
|
|
lastBanner &&
|
|
lastBanner.type === type &&
|
|
lastBanner.message === normalizedMessage &&
|
|
now - lastBanner.createdAt < 1200
|
|
) {
|
|
return lastBanner.id
|
|
}
|
|
|
|
const banner: AppBannerMessage = {
|
|
id: createBannerId(),
|
|
type,
|
|
title: input.title,
|
|
message: normalizedMessage,
|
|
createdAt: now,
|
|
}
|
|
|
|
banners.value = [...banners.value, banner].slice(-MAX_BANNERS)
|
|
return banner.id
|
|
}
|
|
|
|
function pushError(message: string, title = 'Fehler') {
|
|
return push({ type: 'error', title, message })
|
|
}
|
|
|
|
function dismiss(id: string) {
|
|
banners.value = banners.value.filter((banner) => banner.id !== id)
|
|
}
|
|
|
|
function clear() {
|
|
banners.value = []
|
|
}
|
|
|
|
return {
|
|
banners,
|
|
push,
|
|
pushError,
|
|
dismiss,
|
|
clear,
|
|
}
|
|
})
|