610 lines
14 KiB
Markdown

# Notifications Store
The notifications store provides centralized state management for application-wide notifications. It handles the creation, management, and lifecycle of toast-style notifications with support for multiple types, positions, and interactive actions.
## Overview
- **Location**: `src/stores/notifications.js`
- **Type**: Pinia Store
- **Purpose**: Global notification state management
- **Integration**: Used by NotificationDisplay component
## Installation & Setup
```javascript
// Import in your component
import { useNotificationStore } from "@/stores/notifications";
// Use in component
const notificationStore = useNotificationStore();
```
## State Structure
### Core State Properties
```javascript
state: {
notifications: [], // Array of active notifications
defaultDuration: 4000, // Default auto-dismiss time (ms)
maxNotifications: 5, // Maximum concurrent notifications
position: 'top-right', // Default position
nextId: 1 // Auto-incrementing ID counter
}
```
### Notification Object Structure
```javascript
{
id: 1, // Unique identifier
type: 'success', // 'success' | 'error' | 'warning' | 'info'
title: 'Operation Complete', // Notification title
message: 'Data saved successfully', // Main message
duration: 4000, // Auto-dismiss time (0 = no auto-dismiss)
persistent: false, // If true, won't auto-dismiss
actions: [], // Array of action buttons
data: null, // Additional data for handlers
timestamp: '2025-11-12T10:30:00Z', // Creation timestamp
dismissed: false, // Whether notification is dismissed
seen: false // Whether user has interacted with it
}
```
## Getters
### `getNotificationsByType(type)`
Get all notifications of a specific type.
```javascript
const errorNotifications = notificationStore.getNotificationsByType("error");
```
### `activeNotifications`
Get all non-dismissed notifications.
```javascript
const active = notificationStore.activeNotifications;
```
### `activeCount`
Get count of active notifications.
```javascript
const count = notificationStore.activeCount;
```
### `hasErrorNotifications`
Check if there are any active error notifications.
```javascript
const hasErrors = notificationStore.hasErrorNotifications;
```
### `hasSuccessNotifications`
Check if there are any active success notifications.
```javascript
const hasSuccess = notificationStore.hasSuccessNotifications;
```
## Actions
### Core Notification Methods
#### `addNotification(notification)`
Add a new notification with full configuration options.
```javascript
const notificationId = notificationStore.addNotification({
type: "warning",
title: "Confirm Action",
message: "This will permanently delete the item.",
persistent: true,
actions: [
{
label: "Delete",
variant: "danger",
handler: () => performDelete(),
},
{
label: "Cancel",
variant: "secondary",
},
],
data: { itemId: 123 },
});
```
#### Convenience Methods
```javascript
// Quick success notification
notificationStore.addSuccess("Operation completed!");
notificationStore.addSuccess("Custom message", "Custom Title", {
duration: 6000,
});
// Quick error notification
notificationStore.addError("Something went wrong!");
notificationStore.addError("Custom error", "Error Title", { persistent: true });
// Quick warning notification
notificationStore.addWarning("Please confirm this action");
// Quick info notification
notificationStore.addInfo("New feature available");
```
### Notification Management
#### `dismissNotification(id)`
Mark a notification as dismissed (hides it but keeps in history).
```javascript
notificationStore.dismissNotification(notificationId);
```
#### `removeNotification(id)`
Completely remove a notification from the store.
```javascript
notificationStore.removeNotification(notificationId);
```
#### `markAsSeen(id)`
Mark a notification as seen (user has interacted with it).
```javascript
notificationStore.markAsSeen(notificationId);
```
#### `updateNotification(id, updates)`
Update an existing notification's properties.
```javascript
notificationStore.updateNotification(notificationId, {
type: "success",
message: "Updated message",
persistent: false,
});
```
### Bulk Operations
#### `clearType(type)`
Remove all notifications of a specific type.
```javascript
notificationStore.clearType("error"); // Remove all error notifications
```
#### `clearAll()`
Remove all notifications.
```javascript
notificationStore.clearAll();
```
#### `clearDismissed()`
Remove all dismissed notifications from history.
```javascript
notificationStore.clearDismissed();
```
### Loading Notifications
#### `showLoadingNotification(message, title)`
Show a persistent loading notification that can be updated later.
```javascript
const loadingId = notificationStore.showLoadingNotification(
"Uploading file...",
"Please Wait",
);
```
#### `updateToSuccess(id, message, title)`
Update a loading notification to success and auto-dismiss.
```javascript
notificationStore.updateToSuccess(
loadingId,
"File uploaded successfully!",
"Upload Complete",
);
```
#### `updateToError(id, message, title)`
Update a loading notification to error and auto-dismiss.
```javascript
notificationStore.updateToError(
loadingId,
"Upload failed. Please try again.",
"Upload Failed",
);
```
### API Integration Helpers
#### `showApiSuccess(operation, customMessage)`
Show standardized success notifications for API operations.
```javascript
// Uses default messages based on operation
notificationStore.showApiSuccess("create"); // "Item created successfully"
notificationStore.showApiSuccess("update"); // "Item updated successfully"
notificationStore.showApiSuccess("delete"); // "Item deleted successfully"
notificationStore.showApiSuccess("fetch"); // "Data loaded successfully"
// With custom message
notificationStore.showApiSuccess("create", "New client added successfully!");
```
#### `showApiError(operation, error, customMessage)`
Show standardized error notifications for API operations.
```javascript
// Automatically extracts error message from different error formats
notificationStore.showApiError("create", apiError);
notificationStore.showApiError("update", "Network timeout occurred");
notificationStore.showApiError("delete", errorObject, "Custom error message");
```
### Configuration Methods
#### `setPosition(position)`
Set the global notification position.
```javascript
// Available positions:
notificationStore.setPosition("top-right"); // Default
notificationStore.setPosition("top-left");
notificationStore.setPosition("top-center");
notificationStore.setPosition("bottom-right");
notificationStore.setPosition("bottom-left");
notificationStore.setPosition("bottom-center");
```
#### `setDefaultDuration(duration)`
Set the default auto-dismiss duration (milliseconds).
```javascript
notificationStore.setDefaultDuration(5000); // 5 seconds
```
#### `setMaxNotifications(max)`
Set maximum number of concurrent notifications.
```javascript
notificationStore.setMaxNotifications(3);
```
### Advanced Workflow Helper
#### `withNotifications(operation, asyncFunction, options)`
Wrap an async operation with automatic loading/success/error notifications.
```javascript
const result = await notificationStore.withNotifications(
"save",
async () => {
return await saveDataToApi();
},
{
loadingMessage: "Saving changes...",
successMessage: "Changes saved successfully!",
errorMessage: null, // Use default error handling
showLoading: true,
},
);
```
## Action Button Configuration
### Action Object Structure
```javascript
{
label: 'Button Text', // Required: Button label
variant: 'primary', // Optional: 'primary' | 'danger' | 'secondary'
icon: 'mdi mdi-check', // Optional: Icon class
handler: (notification) => {}, // Optional: Click handler function
dismissAfter: true // Optional: Auto-dismiss after click (default: true)
}
```
### Action Examples
```javascript
notificationStore.addNotification({
type: "warning",
title: "Unsaved Changes",
message: "You have unsaved changes. What would you like to do?",
persistent: true,
actions: [
{
label: "Save",
variant: "primary",
icon: "mdi mdi-content-save",
handler: (notification) => {
saveChanges();
// Access notification data if needed
console.log("Saving from notification:", notification.data);
},
},
{
label: "Discard",
variant: "danger",
icon: "mdi mdi-delete",
handler: () => {
discardChanges();
},
},
{
label: "Keep Editing",
variant: "secondary",
dismissAfter: false, // Don't dismiss, let user continue editing
},
],
});
```
## Usage Patterns
### Basic Notifications
```javascript
const notificationStore = useNotificationStore();
// Simple success
notificationStore.addSuccess("Data saved successfully!");
// Simple error
notificationStore.addError("Failed to connect to server");
// Custom duration
notificationStore.addWarning("Session expires in 5 minutes", "Warning", {
duration: 8000,
});
```
### API Operation Notifications
```javascript
// Method 1: Manual handling
try {
await apiCall();
notificationStore.addSuccess("Operation completed successfully!");
} catch (error) {
notificationStore.addError(error.message, "Operation Failed");
}
// Method 2: Using API helpers
try {
await apiCall();
notificationStore.showApiSuccess("create");
} catch (error) {
notificationStore.showApiError("create", error);
}
// Method 3: Using workflow helper
await notificationStore.withNotifications("create", async () => {
return await apiCall();
});
```
### Loading States
```javascript
// Show loading notification
const loadingId = notificationStore.showLoadingNotification("Processing...");
try {
const result = await longRunningOperation();
notificationStore.updateToSuccess(loadingId, "Operation completed!");
} catch (error) {
notificationStore.updateToError(loadingId, "Operation failed");
}
```
### Interactive Notifications
```javascript
// Confirmation dialog
notificationStore.addNotification({
type: "warning",
title: "Delete Confirmation",
message: `Are you sure you want to delete "${itemName}"?`,
persistent: true,
actions: [
{
label: "Yes, Delete",
variant: "danger",
handler: async () => {
const deleteId =
notificationStore.showLoadingNotification("Deleting...");
try {
await deleteItem(itemId);
notificationStore.updateToSuccess(
deleteId,
"Item deleted successfully",
);
refreshData();
} catch (error) {
notificationStore.updateToError(deleteId, "Failed to delete item");
}
},
},
{
label: "Cancel",
variant: "secondary",
},
],
});
// Multi-step workflow
notificationStore.addNotification({
type: "info",
title: "Export Complete",
message: "Your data has been exported successfully.",
actions: [
{
label: "Download",
variant: "primary",
icon: "mdi mdi-download",
handler: () => downloadFile(),
},
{
label: "Email",
variant: "secondary",
icon: "mdi mdi-email",
handler: () => emailFile(),
},
{
label: "View",
variant: "secondary",
icon: "mdi mdi-eye",
handler: () => viewFile(),
},
],
});
```
## Integration with Vue Components
### In Composition API
```javascript
import { useNotificationStore } from "@/stores/notifications";
export default {
setup() {
const notificationStore = useNotificationStore();
const handleSubmit = async () => {
try {
await submitForm();
notificationStore.addSuccess("Form submitted successfully!");
} catch (error) {
notificationStore.addError("Failed to submit form", "Submission Error");
}
};
return {
handleSubmit,
};
},
};
```
### In Options API
```javascript
import { mapActions, mapGetters } from "pinia";
import { useNotificationStore } from "@/stores/notifications";
export default {
computed: {
...mapGetters(useNotificationStore, [
"activeCount",
"hasErrorNotifications",
]),
},
methods: {
...mapActions(useNotificationStore, ["addSuccess", "addError", "clearAll"]),
async saveData() {
try {
await this.apiCall();
this.addSuccess("Data saved!");
} catch (error) {
this.addError(error.message);
}
},
},
};
```
## Best Practices
### Do's
- ✅ Use appropriate notification types for different scenarios
- ✅ Keep messages concise and actionable
- ✅ Use loading notifications for long-running operations
- ✅ Provide clear action buttons for next steps
- ✅ Set appropriate durations (longer for errors, shorter for success)
- ✅ Use persistent notifications for critical actions requiring user input
### Don'ts
- ❌ Don't overwhelm users with too many notifications
- ❌ Don't use persistent notifications for simple confirmations
- ❌ Don't make notification messages too long
- ❌ Don't forget to handle loading state cleanup
- ❌ Don't use success notifications for every small action
### Performance Considerations
- The store automatically limits concurrent notifications
- Dismissed notifications are kept in history for debugging
- Use `clearDismissed()` periodically to prevent memory leaks
- Action handlers should be lightweight to avoid blocking the UI
## Testing
### Unit Testing
```javascript
import { setActivePinia, createPinia } from "pinia";
import { useNotificationStore } from "@/stores/notifications";
describe("Notifications Store", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it("adds notifications correctly", () => {
const store = useNotificationStore();
const id = store.addSuccess("Test message");
expect(store.activeNotifications).toHaveLength(1);
expect(store.activeNotifications[0].message).toBe("Test message");
expect(store.activeNotifications[0].type).toBe("success");
});
it("dismisses notifications", () => {
const store = useNotificationStore();
const id = store.addInfo("Test");
store.dismissNotification(id);
expect(store.activeNotifications).toHaveLength(0);
});
});
```