# Everyday usage

Once the client exists, day-to-day work is mostly: show the inbox, react to clicks, keep the badge honest.


# Toasts vs notifications (please read this)

Same look, different lifetime:

Toast Notification (this service)
Lifespan Seconds Days (until deleted / retention)
Where Floating banner Bell / inbox
After refresh Gone Still there
Transport Local UI state This API + DB
Who owns UI Your app Your app (data from us)
Visual language Prefer the same severity colours, icons, and typography as the inbox Same

It’s normal to use both in one flow — and they should feel like one product:

try {
  await riskyOperation();
  toast.success('Success!'); // ephemeral — your toast host
} catch (error) {
  toast.error('Operation failed'); // same severity styling as inbox
  await notification.error('Failed', error.message, '/help'); // persistent inbox
}

This package does not ship toast.* (or bell markup). Keep toast hosting in the app, but share presentational primitives (severity → colour/icon, title/body layout) between toast and inbox so users aren’t taught two different “notification” looks.


# Configure once, use everywhere

Recommended app pattern:

// lib/notifications.ts
import {
  NotificationsClient,
  createNotificationService,
} from '@inovus-medical/notifications';

export const notificationsClient = new NotificationsClient({
  baseUrl: import.meta.env.VITE_NOTIFICATIONS_URL,
  getAccessToken: () => auth.getValidAccessToken(),
});

// V1-shaped helpers for self-notifications
export const notification = createNotificationService(notificationsClient);

Call sites stay boring and familiar:

import { notification } from './lib/notifications';

await notification.success('Title', 'Message', '/optional-url');
await notification.error('Error Title', 'Error details');
await notification.warning('Warning', 'Something needs attention');
await notification.info('Info', 'Just so you know');

Under the hood each call:

  1. Hits POST /v2/notifications/self
  2. Uses the signed-in user’s sub as recipient (safe — can’t target others)
  3. Adds a unique dedupeKey so that attempt’s retries won’t double-create

# Inbox operations

import { notificationsClient } from './lib/notifications';

// List (newest first). unreadCount is the *total*, not just this page.
const page = await notificationsClient.list({ limit: 50, unreadOnly: true });

await notificationsClient.markRead(id);
await notificationsClient.markUnread(id);
await notificationsClient.markAllRead();
await notificationsClient.delete(id);

const count = await notificationsClient.unreadCount();

When the user clicks a row:

function onNotificationClick(n: Notification) {
  void notificationsClient.markRead(n.id);
  if (n.actionUrl) {
    router.push(n.actionUrl); // your router
  }
}

We store actionUrl; you navigate.


# Keeping the bell up to date

# Option A — subscribe() (framework-agnostic)

const stop = notificationsClient.subscribe(
  ({ items, unreadCount }) => {
    setItems(items);
    setBadge(unreadCount);
  },
  { intervalMs: 30_000 },
);

// logout / teardown
stop();

Behaviour you get for free:

  • ETag / If-None-Match304 when nothing changed
  • Pauses while the browser tab is hidden
  • Refreshes immediately when the tab becomes visible again
  • unreadCount changes outside the first page still wake the listener

# Option B — React hook

import { useNotifications } from '@inovus-medical/notifications/react';

const {
  notifications,
  unreadCount,
  loading,
  error,
  markRead,
  markUnread,
  markAllRead,
  remove,
} = useNotifications(notificationsClient);

Optimistic updates apply locally; the next poll reconciles with the server.


# Severity guide

Severity Typical use
info Neutral FYI
success Something finished well
warning Needs attention, not broken
error Failure the user should know about

Your UI maps these to icons/colours — same four values as V1, so existing mappings often carry over.


# Errors

Failed calls throw NotificationsApiError after retries:

import { NotificationsApiError } from '@inovus-medical/notifications';

try {
  await notificationsClient.markRead(id);
} catch (err) {
  if (err instanceof NotificationsApiError) {
    console.error(err.status, err.problem?.detail ?? err.message);
  }
}

V2 API errors use RFC 9457 application/problem+json. The client parses that for you.


# Logout hygiene

V1 leaked subscriptions because cleanup was easy to forget. In V2:

  • subscribe() returns stop — call it on logout
  • useNotifications cleans up on unmount automatically
  • Create a fresh client (or at least stop polls) when the user session ends

Next: Publishing for backend / M2M flows, or V1 compatibility if you have legacy callers.