@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:
Opened
When the Inbox is opened and a message is in view, we will fire offopened
events. One event per message. We will not send > 1 opened per message.Read/Unread
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.Archive
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.
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:
read:messages
- so we can fetch the messageswrite:events
- so we can create events likeread/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:
- Single list of messages for all messages instead of "Unread/All Messages"
- 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.
- 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:
- Interacting with
useInbox
. See - Intercepting messages with Courier Provider prop onMessage
- Implemented
renderMessage
orrenderAction
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 tooltipstheme.menu
: clicking on the inbox title opens a dropdown menu with options to editpreferences
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 targettheme.message.clickableContainer
which will be ananchor
element instead of adiv
;
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;