14 KiB

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

// Import in your component
import { useNotificationStore } from "@/stores/notifications";

// Use in component
const notificationStore = useNotificationStore();

State Structure

Core State Properties

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

{
  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.

const errorNotifications = notificationStore.getNotificationsByType("error");

activeNotifications

Get all non-dismissed notifications.

const active = notificationStore.activeNotifications;

activeCount

Get count of active notifications.

const count = notificationStore.activeCount;

hasErrorNotifications

Check if there are any active error notifications.

const hasErrors = notificationStore.hasErrorNotifications;

hasSuccessNotifications

Check if there are any active success notifications.

const hasSuccess = notificationStore.hasSuccessNotifications;

Actions

Core Notification Methods

addNotification(notification)

Add a new notification with full configuration options.

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

// 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).

notificationStore.dismissNotification(notificationId);

removeNotification(id)

Completely remove a notification from the store.

notificationStore.removeNotification(notificationId);

markAsSeen(id)

Mark a notification as seen (user has interacted with it).

notificationStore.markAsSeen(notificationId);

updateNotification(id, updates)

Update an existing notification's properties.

notificationStore.updateNotification(notificationId, {
  type: "success",
  message: "Updated message",
  persistent: false,
});

Bulk Operations

clearType(type)

Remove all notifications of a specific type.

notificationStore.clearType("error"); // Remove all error notifications

clearAll()

Remove all notifications.

notificationStore.clearAll();

clearDismissed()

Remove all dismissed notifications from history.

notificationStore.clearDismissed();

Loading Notifications

showLoadingNotification(message, title)

Show a persistent loading notification that can be updated later.

const loadingId = notificationStore.showLoadingNotification(
  "Uploading file...",
  "Please Wait",
);

updateToSuccess(id, message, title)

Update a loading notification to success and auto-dismiss.

notificationStore.updateToSuccess(
  loadingId,
  "File uploaded successfully!",
  "Upload Complete",
);

updateToError(id, message, title)

Update a loading notification to error and auto-dismiss.

notificationStore.updateToError(
  loadingId,
  "Upload failed. Please try again.",
  "Upload Failed",
);

API Integration Helpers

showApiSuccess(operation, customMessage)

Show standardized success notifications for API operations.

// 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.

// 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.

// 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).

notificationStore.setDefaultDuration(5000); // 5 seconds

setMaxNotifications(max)

Set maximum number of concurrent notifications.

notificationStore.setMaxNotifications(3);

Advanced Workflow Helper

withNotifications(operation, asyncFunction, options)

Wrap an async operation with automatic loading/success/error notifications.

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

{
  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

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

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

// 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

// 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

// 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

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

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

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);
  });
});