610 lines
14 KiB
Markdown
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);
|
|
});
|
|
});
|
|
```
|