npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@trycourier/react-inbox

v6.3.2

Published

<img width="584" alt="image" src="https://github.com/trycourier/courier-react/assets/7017640/6ea65539-e3a7-469c-870b-3ddf84793a80">

Downloads

63,505

Readme

What is Inbox?

An in-app notification center list you can use to notify your users. Allows you to build high quality, flexible notification feeds very quickly. Each message supports the following events:

  1. Opened When the Inbox is opened and a message is in view, we will fire off opened events. One event per message. We will not send > 1 opened per message.

  2. Read/Unread

    image

    image

  3. Clicked If a message has a click action associated with it, we will track clicks in the Courier Studio. The message will have a hover effect if the message is clickable as seen below.

    image

  4. Archive

    image

Requirements

Installation

Inbox requires a backend to pull messages. This is all done through the CourierProvider and requires an account at Courier. To set up the Inbox you will need to install the Courier Provider from the integrations page. After installing the integration you will be provided with a Client Key that you will pass into the CourierProvider.

image

Install the following packages to get started:

yarn add @trycourier/react-provider
yarn add @trycourier/react-inbox

@trycourier/react-provider is a peer dependeny of @trycourier/react-inbox and must also be installed

Version 5.X CSP Migration

We've released new subdomains to power Inbox and Toast. This migration only applies to Inbox and Toast users who applied our old URLs to their Content Security Policy.

| Before | After | Usage | | ------------------------------------------------------ | -------------------------- | --------------------------- | | https://api.courier.com | https://api.courier.com | Brands and User Preferences | | wss://1x60p1o3h8.execute-api.us-east-1.amazonaws.com | wss://realtime.courier.com | Websockets | | https://fxw3r7gdm9.execute-api.us-east-1.amazonaws.com | https://inbox.courier.com | Inbox Messages |

Courier Provider

In order for the Inbox component to be placed in the dom you will need to use the CourierProvider. This will handle context and give us access Courier's backend API.

Check here for more information on this concept.

//App.js
import { CourierProvider } from "@trycourier/react-provider";
import { Inbox } from "@trycourier/react-inbox";

function App() {
  // YOUR_CLIENT_KEY is a public facing key and can be found at https://app.courier.com/integrations/courier
  return (
    <CourierProvider userId={yourUserId} clientKey={YOUR_CLIENT_KEY}>
      <Inbox />
    </CourierProvider>
  );
}

Default Inbox Example

The default CourierInbox styles.

//App.js
import { Inbox } from "@trycourier/react-inbox";
import { CourierProvider } from "@trycourier/react-provider";

function App() {
  return (
    <CourierProvider userId={yourUserId} clientKey={yourClientKey}>
      <Inbox />
    </CourierProvider>
  );
}

Styled Inbox Example

Example of a styled CourierInbox.

//App.js
import { Inbox } from "@trycourier/react-inbox";
import { CourierProvider } from "@trycourier/react-provider";

const theme = {
  header: {
    background: "pink",
  },
  messageList: {
    container: {
      background: "pink",
    },
  },
  footer: {
    background: "pink",
  },
  message: {
    container: {
      background: "red",
      "&.read": {
        background: "green",
        div: {
          color: "white",
        },
      },
      "&:not(.read):hover": {
        background: "yellow",
      },
    },
  },
};

function App() {
  return (
    <CourierProvider userId={yourUserId} clientKey={yourClientKey}>
      <Inbox theme={theme} />
    </CourierProvider>
  );
}

Courier Studio Branding (Optional)

You can control your branding from the Courier Studio.

//App.js
import { Inbox } from "@trycourier/react-inbox";
import { CourierProvider } from "@trycourier/react-provider";

function App() {
  return (
    <CourierProvider
      brandId={"BRAND_ID"}
      userId={yourUserId}
      clientKey={yourClientKey}
    >
      <Inbox />
    </CourierProvider>
  );
}

Custom Inbox Example

You can use raw data you can use to build whatever UI you'd like by utilizing our react-hooks package.

Authentication

By default the Courier Provider does not have authentication enabled. This is intentional and is helpful in getting things up and running. All that is required initially is the clientKey and a userId.

Information about the clientKey and authentication configuration can be found at https://app.courier.com/integrations/courier

JWT Authentication (Recommended)

The recommended way of doing authentication with Courier Inbox is to generate a JWT token for each user using the inbox. You can learn more about how to issue a token here: https://www.courier.com/docs/reference/auth/issue-token/

The required scopes are the following:

  1. read:messages - so we can fetch the messages
  2. write:events - so we can create events like read/unread/archive

An example payload to the issue-token api looks like :

{
  "scope": "user_id:MY_USER_ID read:messages write:events"
}

The token that is returned can then be used the following way:

//App.js
import { useState, useEffect } from "react";
import { CourierProvider } from "@trycourier/react-provider";
import { Inbox } from "@trycourier/react-inbox";

function App() {
  const [authorization, setAuthorization] = useState();

  useEffect(() => {
    const response = await fetchAuthToken();
    setAuthentication(response.token);
  }, []);

  return (
    <CourierProvider userId={yourUserId} authorization={authorization}>
      <Inbox />
    </CourierProvider>
  );
}

Token Expiration

If you need your tokens to expire periodically you can pass an expires_in property to the token generation.

{
  "scope": "user_id:MY_USER_ID read:messages write:events",
  "expires_in": "1h"
}
//App.js
import { useState, useEffect } from "react";
import { CourierProvider } from "@trycourier/react-provider";
import { Inbox } from "@trycourier/react-inbox";

function App() {
  const [authorization, setAuthorization] = useState();

  useEffect(() => {
    const response = await fetchAuthToken();
    setAuthorization(response.token);

    const interval = setInterval(async () => {
      const response = await fetchAuthToken();
      setAuthorization(response.token);
    }, 300000);

    return () => clearInterval(interval);
  }, []);

  return (
    <CourierProvider authorization={authorization}>
      <Inbox />
    </CourierProvider>
  );
}

HMAC Authentication (Legacy)

You can also provide an HMAC token to be used. This has been replaced with JWT tokens. Please use JWT over HMAC as JWT allows you to create fine grain scopes and HMAC does not.

HMAC allows you to generate a signature for each user you have in your system. It is a hash of your userId and your API Key.

import crypto from "crypto";

const computedUserHmac = crypto
  .createHmac("sha256", apiKey)
  .update(userId)
  .digest("hex");

Make sure you DO NOT do this on your frontend because your API Key is private and you do not want to leak it. This HMAC should be genrated on the backend and either embedded in your frontend via SSR or you must have an API endpoint to return this value per user. After you have this HMAC returned, you can provide it to the CourierProvider property. This is typically done inside an api that returns user information. i.e. GET /user/:user-id

import { CourierProvider } from "@trycourier/react-provider";
import { Inbox } from "@trycourier/react-inbox";

const MyComponent = (props) => {
  return (
    <CourierProvider
      userId={props.userId}
      userSignature={props.computedUserHmac}
      clientKey={process.env.COURIER_CLIENT_KEY}
    >
      <Inbox />
      {props.children}
    </CourierProvider>
  );
};

Props

interface IHeaderProps {
  labels: InboxProps["labels"];
  markAllAsRead: () => void;
  messages: IInboxMessagePreview[];
  title?: string;
  unreadMessageCount?: number;
}

interface ITextBlock {
  type: "text";
  text: string;
}

interface IActionBlock {
  type: "action";
  text: string;
  url: string;
}

interface IGetInboxMessagesParams {
    tenantId?: string;
    archived?: boolean;
    from?: string | number;
    limit?: number;
    status?: "read" | "unread";
    tags?: string[];
}

interface InboxProps {
  tenantId?: string;
  brand?: Brand;
  className?: string;
  defaultIcon?: false | string;

  // start date of the inbox to fetch messages
  from?: number;

  isOpen?: boolean;

  // allows different views with different filter params
  views?: Array<{
    id: string;
    label: string;
    params?: IGetInboxMessagesParams;
  }>;
  formatDate?: (isoDate: string) => string;

  // html query selector to render the inbox into
  appendTo?: string;
  labels?: {
    archiveMessage?: string;
    backToInbox?: string;
    closeInbox?: string;
    emptyState?: string;
    markAllAsRead?: string;
    markAsRead?: string;
    markAsUnread?: string;
    scrollTop?: string | ((count: string) => string);
  };

  // event listener for events such as "read", "unread", "archive"
  onEvent?: OnEvent;
  openLinksInNewTab?: boolean;

  // relative to the inbox beel
  placement?: TippyProps["placement"];
  showUnreadMessageCount?: boolean;
  theme?: InboxTheme;
  title?: string;
  trigger?: TippyProps["trigger"];
  renderContainer?: React.FunctionComponent;
  renderBell?: React.FunctionComponent<{
    className?: string;
    isOpen: boolean;
    onClick?: (event: React.MouseEvent) => void;
  }>;
  renderFooter?: React.FunctionComponent;
  renderHeader?: React.FunctionComponent<IHeaderProps>;
  renderPin?: React.FunctionComponent<PinDetails>;
  renderIcon?: React.FunctionComponent<{
    isOpen: boolean;
    unreadMessageCount?: number;
  }>;
  renderMessage?: React.FunctionComponent<IInboxMessagePreview>;
  renderNoMessages?: React.FunctionComponent;
}

Views

You can add more views beyound the default "all messages" view. You can provide a few different filter params like archived, status, limit, tags, ect... to create the experience you are looking for. If you customize the views, you will overwrite the default view of:

{
  "id": "messages",
  "label": "Notifications"
}

so make sure to include this view if you want to include an all messages view.

Hooks

useInbox is a hook that you can import and use to interact with Inbox without having to use any of the react components. Think of it as a headless Inbox.

See https://github.com/trycourier/courier-react/tree/main/packages/react-hooks

Theme

interface ITheme {
  brand?: Brand;
  container?: CSSObject;
  emptyState?: CSSObject;
  footer?: CSSObject;
  header?: CSSObject;
  menu?: CSSObject;
  tooltip?: CSSObject;
  icon?: CSSObject & {
    open?: string;
    closed?: string;
  };
  messageList?: {
    container?: CSSObject;
    scrollTop?: CSSObject;
  };
  message?: {
    actionElement?: CSSObject;
    actionMenu?: {
      button?: CSSObject;
      dropdown?: CSSObject;
    };
    clickableContainer?: CSSObject;
    container?: CSSObject;
    content?: CSSObject;
    icon?: CSSObject;
    textElement?: CSSObject;
    timeAgo?: CSSObject;
    title?: CSSObject;
    unreadIndicator?: CSSObject;
  };
  root?: CSSObject;
  unreadIndicator?: CSSObject;
}

Since we are themeing with CSSObject from styled components, there are some themes that you may need to target by specifiying classNames. For example, to theme the read message styling you would do:

const theme = {
  message: {
    container: {
      "&.read": {
        background: "red",
      },
      "&:not(.read):hover": {
        background: "blue",
      },
    },
  },
};

Render Props

Render Props are a react concept that allows you to supply your own react components instead of the ones built for this library. Inbox supplies render props for most sub components.

To overrwrite the rendering of each of these you can supply your own react component.

// Render Props for Custom Rendering
  renderBlocks?: {
    action?: React.FunctionComponent<IActionBlock>;
    text?: React.FunctionComponent<ITextBlock>;
  };
  renderContainer?: React.FunctionComponent;
  renderBell?: React.FunctionComponent<{
    className?: string;
    isOpen?: boolean;
    onClick?: (event: React.MouseEvent) => void;
    onMouseEnter?: (event: React.MouseEvent) => void;
  }>;
  renderFooter?: React.FunctionComponent;
  renderHeader?: React.FunctionComponent<IHeaderProps>;
  renderPin?: React.FunctionComponent<PinDetails>;
  renderIcon?: React.FunctionComponent<{
    isOpen: boolean;
    unreadMessageCount?: number;
  }>;
  renderMessage?: React.FunctionComponent<IMessage>;
  renderNoMessages?: React.FunctionComponent;

Pinning

Pinning is a new feature as of 3.6.0 that allows you to "pin" certain messages to the top of their inbox. The pins are configured into slots by editing your brand in the Courier Studio or by passing in a brand object with the correct pin slots. A pin slot is defined as:

interface PinSlot {
  id: string;
  label: {
    value?: string;
    color?: string;
  };
  icon: {
    value?: string;
    color?: string;
  };
}

The default Pin looks like:

They can be configured to look like:


You can override the styling of the Pin through css accessing theme?.message?.pinned or by passing in a renderPin(pinSlot) as a property to the component.

3.X Breaking Changes:

The classic styling of the inbox has been deprecated. You can find more information about the old styling here. In summary, you can access the old styling and non-breaking changes by installing the 2.0.1 version linked above for @trycourier/react-inbox and @trycourier/react-provider.

Updated Theme: Some of the main differences are the following:

  1. Single list of messages for all messages instead of "Unread/All Messages"
  2. Messages with one action block will now be clickable instead of rendering a button. There is a hover effect on the message to let the user know they can click on the entire message.
  3. Archiving is message is now available via the UI

Message Interface

The format of the message has changd, so if you have any code that utilizes any of the following you will need to update:

  1. Interacting with useInbox. See
  2. Intercepting messages with Courier Provider prop onMessage
  3. Implemented renderMessage or renderAction

This is a contrived example of the changes:

Note we are utilized our new elemental standard:

interface ActionBlock {
  type: "text";
  text: string;
  url: string;
}

interface OldMessage {
  title: string;
  body: string;
  read?: boolean;
  blocks: Array<TextBlock | ActionBlock>;
}

interface ActionElement {
  type: "text";
  content: string;
  href: string;
}

interface NewMessage {
  title: string;
  preview: string;
  read?: string;
  actions: Array<ActionElement>;
}

Theme

  • theme.tabList -> deprecated
  • theme.message.actionBlock
    • the entire message is now clickable when you have 1 button
    • when 2 buttons you use theme.message.actionElement to style
  • theme.message.textBlock -> theme.message.textElement

New Theme Properties:

  • theme.tooltip: accesses background and colors of tooltips
  • theme.menu: clicking on the inbox title opens a dropdown menu with options to edit preferences
  • theme.message.clickableContainer: when a message has an action href, we now make the entire message clickable instead of rendering an explicit button. this theme property allows access to this component. theme.message.container will still apply to this component but if you want to target the clickableContainer separatly you can target theme.message.clickableContainer which will be an anchor element instead of a div;

Using Custom Date Format

The inbox component accepts a function in property formatDate of type (isoDate: string) => string;. You can overwrite Courier's date formats on each message using formatDate for use cases like internationalization and override the browser's default.

Example using date-fns:

import formatDistanceStrict from "date-fns/formatDistanceStrict";
import { Locale } from "date-fns";

const getTimeAgo = (created: string, locale: Locale) => {
  return formatDistanceStrict(new Date(created).getTime(), Date.now(), {
      addSuffix: true,
      roundingMethod: "floor",
      locale
    })(created);
}

---

<Inbox
  formatDate={isoDate=>getTimeAgo(isoDate, users_locale)}
>

Listening to Events

You can listen to inbox events by passing onEvent prop to

type EventType = "read" | "unread" | "archive" | "click" | "mark-all-read" | "unpin";
interface IEventParams = {
  message?: IInboxMessagePreview;
  messageId?: string;
  event: EventType;
}
type OnEvent = (params: IEventParams) => void;