npm package: @1saifj/novu-client
Description: Custom Novu client library for React and Next.js
Latest version: 1.2.0

# @1saifj/novu-client

A modern React client library for [Novu](https://novu.co) notification infrastructure with a beautiful Tailwind CSS UI and comprehensive theming options.

[![npm version](https://img.shields.io/npm/v/@1saifj/novu-client.svg)](https://www.npmjs.com/package/@1saifj/novu-client)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/1saifj/novu-client-lib/blob/main/LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/1saifj/novu-client-lib/pulls)

<p align="center">
  <img src="https://i.imgur.com/YLbGg6h.png" alt="@1saifj/novu-client Preview" width="600">
</p>

## 🌟 Features

- **Modern API Client**: Fully typed Novu API client with comprehensive endpoint coverage
- **Real-time Support**: WebSocket integration for instant notifications
- **Beautiful UI**: Clean, responsive Tailwind CSS components
- **Flexible Theming**: CSS variables and provider-based theming system
- **Customizable UI**: Replace any component with your own custom UI
- **Pagination Support**: Built-in "Load More" functionality
- **TypeScript Ready**: Full type definitions for a seamless developer experience
- **React Hooks**: Simple integration with functional components

## 📦 Installation

```bash
npm install @1saifj/novu-client
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install react react-dom
```

## 🚀 Quick Start

### Basic Usage

```jsx
import React from 'react';
import { Inbox, NovuThemeProvider } from '@1saifj/novu-client';

function NotificationsComponent() {
  return (
    <NovuThemeProvider>
      <Inbox 
        options={{
          applicationIdentifier: 'YOUR_NOVU_APP_ID',
          subscriberId: 'user-123',
          backendUrl: 'https://api.novu.co/v1',
          socketUrl: 'wss://ws.novu.co'
        }}
        onNotificationClick={(notification) => {
          console.log('Notification clicked:', notification);
        }}
        onUnseenCountChanged={(count) => {
          console.log('Unseen count:', count);
        }}
      />
    </NovuThemeProvider>
  );
}
```

## 🎨 Theming

### Theme Customization

Customize the appearance by providing a theme object to the `NovuThemeProvider`:

```jsx
import React from 'react';
import { Inbox, NovuThemeProvider } from '@1saifj/novu-client';

function ThemedNotifications() {
  const theme = {
    colors: {
      primary: '#3B82F6', // Blue theme
      secondary: '#1F2937',
      grayLight: '#F9FAFB',
      grayMedium: '#E5E7EB',
      grayDark: '#6B7280',
      success: '#10B981',
      warning: '#F59E0B',
      error: '#EF4444'
    },
    borderRadius: {
      md: '0.375rem',
      lg: '0.5rem',
    },
    container: {
      maxWidth: '32rem',
      maxHeight: '500px',
    }
  };

  return (
    <NovuThemeProvider theme={theme}>
      <Inbox 
        options={{
          applicationIdentifier: 'YOUR_NOVU_APP_ID',
          subscriberId: 'user-123',
          backendUrl: 'https://api.novu.co/v1',
          socketUrl: 'wss://ws.novu.co'
        }}
      />
    </NovuThemeProvider>
  );
}
```

### CSS Variables

You can also override the CSS variables directly in your CSS:

```css
:root {
  --novu-primary: #3B82F6;
  --novu-secondary: #1F2937;
  --novu-gray-light: #F9FAFB;
  --novu-gray-medium: #E5E7EB;
  --novu-gray-dark: #6B7280;
  --novu-container-max-width: 32rem;
  --novu-container-max-height: 500px;
  --novu-border-radius-lg: 0.5rem;
}
```

### Class Name Overrides

Add custom classes to specific components:

```jsx
const theme = {
  classNameOverrides: {
    inboxContainer: 'my-custom-inbox-container',
    notification: 'my-custom-notification',
    unseenNotification: 'my-custom-unseen',
    loadingState: 'my-custom-loading',
    emptyState: 'my-custom-empty',
    button: 'my-custom-button'
  }
};
```

## 🛠️ Advanced Usage

### Custom UI Components

Replace any part of the Inbox UI with your own components:

```jsx
import React from 'react';
import { Inbox, NovuThemeProvider } from '@1saifj/novu-client';

function CustomizedInbox() {
  return (
    <NovuThemeProvider>
      <Inbox 
        options={{
          applicationIdentifier: 'YOUR_NOVU_APP_ID',
          subscriberId: 'user-123',
          backendUrl: 'https://api.novu.co/v1',
          socketUrl: 'wss://ws.novu.co'
        }}
        headerComponent={
          <div className="flex justify-between items-center p-4 border-b border-gray-200">
            <h2 className="text-xl font-bold">My Notifications</h2>
            <button className="text-blue-500 hover:text-blue-700">
              Settings
            </button>
          </div>
        }
        emptyState={
          <div className="flex flex-col items-center justify-center p-8 text-center">
            <img src="/empty-state.svg" alt="No notifications" className="w-24 h-24 mb-4" />
            <p className="text-gray-500">You're all caught up!</p>
          </div>
        }
        footerComponent={
          <div className="p-3 bg-gray-50 border-t border-gray-200 text-center">
            <a href="/preferences" className="text-sm text-blue-500 hover:underline">
              Manage notification preferences
            </a>
          </div>
        }
      />
    </NovuThemeProvider>
  );
}
```

### Direct API Usage

Use the Novu client directly for custom integrations:

```jsx
import { NovuClient } from '@1saifj/novu-client';

async function notificationService() {
  // Initialize client
  const client = new NovuClient({
    backendUrl: 'https://api.novu.co/v1',
    socketUrl: 'wss://ws.novu.co',
    applicationIdentifier: 'YOUR_NOVU_APP_ID',
    subscriberId: 'user-123'
  });
  
  // Start session
  await client.initSession();
  
  // Get notifications feed
  const feed = await client.getNotificationFeed('user-123', { 
    page: 0, 
    limit: 10 
  });
  
  // Mark message as read
  await client.markMessageAs('user-123', {
    messageId: 'message-id',
    mark: { seen: true, read: true }
  });
  
  // Mark all messages as read
  await client.markAllMessagesAs('user-123', {
    markAs: 'read'
  });
  
  // Get subscriber preferences
  const preferences = await client.getSubscriberPreferences('user-123');
  
  // Get unseen count
  const unseenCount = await client.getUnseenCount('user-123');
  
  // Listen for real-time notifications
  client.connectWebSocket();
  client.onNotification((notification) => {
    console.log('New notification:', notification);
  });
}
```

### Solving CORS Issues

When using the library in a browser environment, you might encounter CORS issues when making requests to the Novu API. To solve this, you can use the `proxyUrl` option which routes requests through your own proxy server:

```jsx
import React from 'react';
import { Inbox, NovuThemeProvider } from '@1saifj/novu-client';

function NotificationsComponent() {
  return (
    <NovuThemeProvider>
      <Inbox 
        options={{
          applicationIdentifier: 'YOUR_NOVU_APP_ID',
          subscriberId: 'user-123',
          backendUrl: 'https://api.novu.co/v1', // Original API URL
          socketUrl: 'wss://ws.novu.co',
          proxyUrl: '/api/novu-proxy' // Your proxy endpoint
        }}
        onNotificationClick={(notification) => {
          console.log('Notification clicked:', notification);
        }}
      />
    </NovuThemeProvider>
  );
}
```

You can implement a simple proxy in Next.js:

```javascript
// pages/api/novu-proxy/[...path].js
export default async function handler(req, res) {
  const { path } = req.query;
  const apiUrl = `https://api.novu.co/v1/${path.join('/')}`;
  
  try {
    const response = await fetch(apiUrl, {
      method: req.method,
      headers: {
        ...(req.headers.authorization && { 'Authorization': req.headers.authorization }),
        'Content-Type': 'application/json'
      },
      body: req.method !== 'GET' && req.body ? JSON.stringify(req.body) : undefined
    });
    
    const data = await response.json();
    return res.status(response.status).json(data);
  } catch (error) {
    return res.status(500).json({ error: 'Failed to proxy request' });
  }
}
```

## 📋 API Reference

### Components

#### `<NovuThemeProvider>`

Provides theming context to all Novu components.

| Prop | Type | Description |
|------|------|-------------|
| `theme` | `NovuTheme` | Theme configuration object |
| `children` | `ReactNode` | Child components |

#### `<Inbox>`

The main notification inbox component.

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `options` | `object` | ✅ | Configuration for the Novu client |
| `options.applicationIdentifier` | `string` | ✅ | Your Novu application ID |
| `options.subscriberId` | `string` | ✅ | Current user's subscriber ID |
| `options.backendUrl` | `string` | ✅ | Novu API URL |
| `options.socketUrl` | `string` | ✅ | Novu WebSocket URL |
| `options.proxyUrl` | `string` | | Optional proxy URL to bypass CORS issues |
| `onNotificationClick` | `function` | | Callback when a notification is clicked |
| `onUnseenCountChanged` | `function` | | Callback when unseen count changes |
| `className` | `string` | | Additional CSS class for the inbox container |
| `headerComponent` | `ReactNode` | | Custom header component |
| `footerComponent` | `ReactNode` | | Custom footer component |
| `emptyState` | `ReactNode` | | Custom empty state component |
| `loadingState` | `ReactNode` | | Custom loading state component |

### Client Methods

#### `NovuClient`

| Method | Parameters | Return Type | Description |
|--------|------------|-------------|-------------|
| `constructor` | `NovuOptions` | `NovuClient` | Creates a new client instance with options including `proxyUrl` for CORS handling |
| `initSession` | `subscriberId?: string` | `Promise<any>` | Initialize a session with the Novu API |
| `connectWebSocket` | | `any` | Connect to the Novu WebSocket for real-time updates |
| `onNotification` | `callback: (notification: Message) => void` | `void` | Register a callback for real-time notifications |
| `getNotificationFeed` | `subscriberId: string, options?: object` | `Promise<FeedResponse>` | Get notification feed for a subscriber |
| `getUnseenCount` | `subscriberId: string, feedIdentifier?: string` | `Promise<UnseenCountResponse>` | Get count of unseen notifications |
| `markMessageAs` | `subscriberId: string, options: MarkMessagesOptions` | `Promise<any>` | Mark a message as read/unread or seen/unseen |
| `markAllMessagesAs` | `subscriberId: string, options: MarkAllMessagesOptions` | `Promise<any>` | Mark all messages with a specific status |
| `updateOnlineStatus` | `subscriberId: string, status: OnlineStatusUpdate` | `Promise<Subscriber>` | Update subscriber's online status |
| `getSubscriberPreferences` | `subscriberId: string` | `Promise<Preferences[]>` | Get subscriber's notification preferences |
| `updateSubscriberPreferences` | `subscriberId: string, templateId: string, preferences: SubscriberPreference` | `Promise<Preferences>` | Update subscriber's preferences |

### Types

#### `NovuTheme`

```typescript
interface NovuTheme {
  colors?: {
    primary?: string;
    secondary?: string;
    grayLight?: string;
    grayMedium?: string;
    grayDark?: string;
    success?: string;
    warning?: string;
    error?: string;
  };
  spacing?: {
    1?: string;
    2?: string;
    3?: string;
    4?: string;
    8?: string;
  };
  borderRadius?: {
    sm?: string;
    md?: string;
    lg?: string;
    full?: string;
  };
  typography?: {
    fontSizeXs?: string;
    fontSizeSm?: string;
    fontSizeBase?: string;
    fontSizeLg?: string;
    fontWeightNormal?: number;
    fontWeightMedium?: number;
    fontWeightSemibold?: number;
  };
  shadow?: string;
  container?: {
    maxWidth?: string;
    maxHeight?: string;
  };
  classNameOverrides?: {
    inboxContainer?: string;
    inboxHeader?: string;
    notificationsList?: string;
    notification?: string;
    unseenNotification?: string;
    loadingState?: string;
    emptyState?: string;
    button?: string;
  };
}
```

#### `Message` (Notification)

```typescript
interface Message {
  _id: string;
  title?: string;
  content: string;
  seen: boolean;
  read: boolean;
  createdAt: string;
  templateId?: string;
  payload?: Record<string, any>;
  cta?: {
    type: string;
    data: Record<string, any>;
  };
  [key: string]: any;
}
```

## 💡 Tips & Best Practices

1. **Theme Provider**: Always wrap your Novu components with `NovuThemeProvider` to ensure proper styling, even if you're not customizing the theme.

2. **Error Handling**: Implement proper error handling around API calls to gracefully handle network issues.

3. **Subscriber ID**: Make sure to use consistent subscriber IDs across your application to ensure notifications are properly associated with users.

4. **Performance**: If you have a large number of notifications, consider implementing virtualized lists for better performance.

5. **Authentication**: Ensure you have proper authentication for your Novu API calls in production.

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

---

Built with ❤️ by [@1saifj](https://github.com/1saifj)