feat: centralized error notification service (issue #15)

- Create NotificationService with context provider for centralized error/success messaging
- Add NotificationContainer component to render notification stack
- Integrate NotificationProvider into App root
- Refactor BanUnbanForm to use notification service instead of local error state
- Update fetchError utility to optionally use notification callbacks
- Add comprehensive error handling guidelines to Web-Development.md
- Prevent duplicate notifications with deduplication logic
- Support auto-dismiss with configurable TTL per notification type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-28 08:41:33 +02:00
parent da6433b2cf
commit ae34d98859
8 changed files with 557 additions and 152 deletions

View File

@@ -0,0 +1,86 @@
/**
* Notification display container.
*
* Renders all active notifications from the NotificationService as a vertical stack
* at the top of the application. Each notification displays with appropriate styling
* based on its intent level and auto-dismisses after a configured duration.
*/
import { MessageBar, MessageBarBody, makeStyles, tokens } from "@fluentui/react-components";
import { useNotificationQueue } from "../services/notificationService";
import type { Notification } from "../types/notification";
/** Styles for the notification container and messages. */
const useStyles = makeStyles({
container: {
position: "fixed",
top: 0,
left: 0,
right: 0,
zIndex: 10000, // Above all other content
pointerEvents: "none", // Allow clicks to pass through to content below
padding: tokens.spacingVerticalL,
maxHeight: "60vh",
overflowY: "auto",
},
stack: {
display: "flex",
flexDirection: "column",
gap: tokens.spacingVerticalM,
maxWidth: "600px",
margin: "0 auto",
},
messageBar: {
pointerEvents: "auto", // But restore pointer events on the messages
},
});
/**
* NotificationContainer renders the notification queue.
*
* Place this component once in your app root (e.g., in App.tsx) to display
* all notifications managed by the NotificationService.
*
* @returns JSX element rendering the notification stack or empty fragment if no notifications.
*/
export function NotificationContainer(): React.JSX.Element {
const styles = useStyles();
const notifications = useNotificationQueue();
if (notifications.length === 0) {
return <></>;
}
return (
<div className={styles.container} role="alert" aria-live="polite">
<div className={styles.stack}>
{notifications.map((notification) => (
<NotificationMessage key={notification.id} notification={notification} />
))}
</div>
</div>
);
}
/**
* Single notification message component.
* Renders a Fluent UI MessageBar with appropriate styling based on notification intent.
*/
interface NotificationMessageProps {
notification: Notification;
}
function NotificationMessage({ notification }: NotificationMessageProps): React.JSX.Element {
const styles = useStyles();
// Map notification intent to MessageBar intent
const messageBarIntent: "success" | "error" | "warning" | "info" =
notification.intent === "info" ? "info" : notification.intent;
return (
<MessageBar intent={messageBarIntent} className={styles.messageBar}>
<MessageBarBody>{notification.message}</MessageBarBody>
</MessageBar>
);
}