feat: das Dashboard -- und die eine Regel steht im Code, nicht im Vorsatz
Schritt 4. React 18, TypeScript, Vite 5, Tailwind 4 (CSS-first), Lucide, Zustand; kein Konsta. Quelle in server/ui, Build nach server/web (gitignored), im Abbild von einer eigenen Docker-Stufe gebaut. JEDER KNOPF GIBT RUECKMELDUNG -- und zwar erzwungen, nicht vorgenommen: - api.ts haelt den EINZIGEN fetch und GENAU EINE Fehlerklasse. Der Fivemanage-Fehler war, zwei zu haben: geworfen wurde `new Error`, geprueft auf `instanceof ApiError`, und damit verschwand jeder Fehlschlag lautlos. - store.ts haelt run(): Aktion rein, Erfolgstext rein, und es meldet bei Erfolg diesen und bei Fehlschlag den Text DES DIENSTES. Wer darueber geht, kann keinen stillen Knopf bauen. - Jeder wartende Knopf ist gesperrt und zeigt einen Kreisel. Der zweite Klick eine Sekunde spaeter war der Weg zu neunzehn gleichnamigen Organisationen. - Fehlermeldungen bleiben stehen, bis jemand sie wegklickt. Ein Fehler, der von allein verschwindet, ist einer, den niemand gelesen hat. @vitejs/plugin-react ist auf ^4 festgenagelt: die 6 verlangt vite ^8. In den peerDependencies nachgesehen -- Vite 5.4.21 liegt in der Schnittmenge von plugin-react@4 und @tailwindcss/vite@4. Am laufenden Container nachgemessen: / kommt als text/html, assets/*.js als text/javascript, *.css als text/css (die MIME-Falle aus Schritt 1 traegt), und /tokens, /speicher, /konto liefern dieselbe index.html -- die SPA-Rueckfall- route tut, wofuer sie gebaut wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { CheckCircle2, AlertTriangle, Info, X } from 'lucide-react'
|
||||
import { useStore, type NoteKind } from '../store'
|
||||
|
||||
const gestalt: Record<NoteKind, { farbe: string; kind: typeof Info }> = {
|
||||
ok: { farbe: 'border-gut/40 bg-gut/10 text-gut', kind: CheckCircle2 },
|
||||
fehler: { farbe: 'border-schlecht/40 bg-schlecht/10 text-schlecht', kind: AlertTriangle },
|
||||
hinweis: { farbe: 'border-akzent/40 bg-akzent/10 text-akzent', kind: Info },
|
||||
}
|
||||
|
||||
/** Die Rückmeldungen, unten rechts.
|
||||
*
|
||||
* Erfolge verschwinden nach vier Sekunden von allein, Fehler bleiben stehen,
|
||||
* bis jemand sie wegklickt — das entscheidet der Laden, nicht diese Ansicht. */
|
||||
export function Meldungen() {
|
||||
const notes = useStore((s) => s.notes)
|
||||
const dismiss = useStore((s) => s.dismiss)
|
||||
|
||||
if (notes.length === 0) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed right-4 bottom-4 z-60 flex w-[min(26rem,calc(100vw-2rem))] flex-col gap-2"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{notes.map((n) => {
|
||||
const { farbe, kind: Kind } = gestalt[n.kind]
|
||||
return (
|
||||
<div
|
||||
key={n.id}
|
||||
className={`flex items-start gap-2 rounded-md border px-3 py-2 text-sm
|
||||
shadow-lg backdrop-blur ${farbe}`}
|
||||
>
|
||||
<Kind size={16} className="mt-0.5 shrink-0" />
|
||||
<span className="flex-1 break-words text-schrift">{n.text}</span>
|
||||
<button
|
||||
onClick={() => dismiss(n.id)}
|
||||
className="shrink-0 rounded p-0.5 text-schrift-leise hover:text-schrift"
|
||||
aria-label="Meldung schließen"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user