@trycourier/react-inbox
TypeScript icon, indicating that this package has built-in type declarations

5.1.2 • Public • Published

image

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

Requirement Reason
Courier Inbox Provider Needed to link your Courier Inbox to the SDK
Authentication Needed to view inbox messages that belong to a user.
Courier React Provider Provides state, api connections, and context to the components.

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

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

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

The default CourierInbox styles.

inbox-banner

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

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

Example of a styled CourierInbox.

image

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

setting

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

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

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

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

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

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

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.

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

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

image

They can be configured to look like:

image


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.

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

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

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

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;

Readme

Keywords

none

Package Sidebar

Install

npm i @trycourier/react-inbox

Weekly Downloads

18,975

Version

5.1.2

License

ISC

Unpacked Size

207 kB

Total Files

115

Last publish

Collaborators

  • mikemilla
  • troygoode
  • scarney