# TOPBAR

The shared Planning Center product Topbar

In general, it is responsible for a number of things that are shared across our apps.

- App switcher (via [`AppsProvider`](./modules/apps_provider.tsx))
- Profile switcher (via [`ConnectedPeopleProvider`](./modules/connected_people_provider.tsx))
- Admin notifications inbox dropdown (via [`NotificationsMenu`](./modules/components/notifications_menu.tsx))
- Platform and/or product announcements (via [`PlatformAnnouncements`](./modules/platform_announcements.jsx) and [`ProductAnnouncements`](./moduels/product_announcements))

## Usage

To a limited extent, each product can customize the display of these items as they see fit.
For the majority of the products, implementation is very similar.
You can view [product-specific examples](#example-product-implementations) below.

### Disabling the notifications inbox

The global determination of whether the notifications inbox is enabled or disabled is controlled via a [People flipper flag](https://people.planningcenteronline.com/flipper/features/TOPBAR_notifications_enabled).
However, if your app needs to disable the notifications inbox for whatever reason, set `window.notificationsDisabled = true` when your app renders and we'll honor that setting.
Because settings and flags are cached for one load cycle, it will take a fresh reload from the user to have the disable override to take effect.

### Shared Jolt connections

New in v7.3 is the admin notifications inbox feature.
Visually, this is a bell in the topbar that, when clicked, will display a dropdown containing notifications from every app in their account.
Because a notification can be sent to a user as they are working on a page, topbar subscribes to a special notifications channel for the current user that provides push updates to the dropdown.

For a handful of reasons (not least of which is because we pay per Jolt connection), if the current page of an app is already using a Jolt connection, topbar would like to piggy-back on that connection for its own subscription.
**Note that this connection sharing is entirely optional for the parent app, but strongly encouraged.**
If a Jolt connection is not shared with topbar, it will gladly initiate its own connection specifically for its purposes.

To reuse your app's Jolt connection, topbar requires a Promise that resolves into a [Jolt client](https://github.com/planningcenter/jolt-client/) connection.
If nothing is passed&mdash;or if a Promise is passed that resolves to `undefined` or `void`&mdash;topbar will initiate its own connection.

Topbar hooks into `pco-page-expired` and will `disconnect` when the session is invalidated (i.e. logged out in a different tab).
This occurs for `topbar`-created jolt instances as well as jolt instances provided to `topbar` via the `joltPromise` prop.
CSRF tokens are invalidated when the session is invalidated.
Non-GET APIv2 requests (i.e. auth/subscribe) require a valid CSRF token and will never succeed in that state.

#### Example

As an example, we'll use what Services has done for the v7.3 alpha.
As a layout partial, they attach two Jolt-related things to `window`:

1. `joltListener` (an instance of their own wrapper around the `jolt-client` library)
1. `joltListenerPromise` (a `jolt-client` instance that has gone through (or will go through asynchronously) the connection and authorization process)

```html
<script>
  window.joltListener = new window.JoltListener()
  window.joltListenerPromise = window.joltListener.connect()
</script>
```

In their Topbar implementation component, they then create the function that provides topbar the required Promise...

```jsx
const waitForWindowJolt = async () =>
  window.joltListenerPromise.then((listener) => {
    if (listener && listener.jolt) {
      return listener.jolt
    }
  })
```

...and passes that function in for the `JoltPromise` prop to topbar.
Topbar can then use this Promise to determine whether or not it needs to create its own connection or piggyback on one provided.

The implementation details for your app can be decided by you and dependent on your apps current usage of Jolt.

## Upgrade

```bash
yarn add @planningcenter/topbar
```

## Develop

```bash
cd ~/Code/planningcenter/design/planningcenter/topbar
yarn install
yarn dev
```

The `dev` script starts a server at [http://localhost:9000](http://localhost:9000).

The [`dev/dev.tsx`](./dev/dev.tsx) is the main dev environment component.

### Chat development (Toolbar)

In order for the Chat tool to fetch your Stream User, the component needs your `planning_center_session` cookie.

Visiting the Topbar dev server at [people.pco.test:9000](http://people.pco.test:9000) (or at port `9000` on any of the other apps listed in your hosts file) will provide the cookie.

### Local App Development

If you are working on a new feature or run into a specific bug in your application and need to inspect the problem locally before publishing, you can use [`yalc`](https://github.com/whitecolor/yalc#yalc) to install and run the Topbar package locally.

To get started:

1. In `/topbar`: `yalc publish`
2. In your product: `yalc add @planningcenter/topbar`

When you make `topbar` changes, run `yalc publish --push` to update your product.

When you're done, in your product directory run `yalc remove @planningcenter/topbar`.

If you are not seeing published updates after running `yalc publish --push` or things break in your app after running `yalc remove @planningcenter/topbar` then in your product you will want to be running `bin/webpack-dev-server`.
From experience, we have seen that the People app will automatically reload while running `bin/webpack-dev-server`.
Unfortunately, in the Services app we have had to restart `bin/webpack-dev-server` every time for a change to come in.
This may change depending on what app you're adding your yalc changes to.

### Debugging Jolt

The `jolt-client` library has the ability to log debug information to the console.
This functionality is configured when creating a client and is disabled by default.
If you are passing your own Jolt client object into Topbar via props, you can configure it as required before providing it to Topbar.
If you are relying on Topbar to create its own Jolt connection and would like to enable logging, pass `enableJoltLogging={true}` as a prop to whichever topbar components you are using
you are using (`AppsProvider`/`Tasks`/`Toolbar`).

## Toolbar

Toolbar lives alongside Topbar on the right side of the viewport. It is responsible for the global concerns of Profile, Help Desk, Tasks, Notifications, and Core Messaging.

### Contributing

Tool implementations are self-contained modules that live in the `modules/toolbar/tools/` directory.

A tool module is required to have a named export which adheres to the following `ToolbarTool` interface.

```ts
interface ToolbarTool {
  component: unknown // A React component. Stronger typing is in the works.
  icon: string
  name: string
}
```

- `component`: the React component that will be rendered into the drawer when the tool is selected.
- `icon`: the name of the [`tapestry-react` icon](https://planningcenter.github.io/tapestry-react/icon#preview-icons) that will be rendered as the button in Toolbar.
- `name`: the formal name of the tool. For now, this is only surfaced for accessibility concerns on the tool's toolbar `Button`.

### Example

The following example implements an example tool using the shared `DrawerHeader` and `DrawerHeading` components.

- `DrawerHeader` handles the placement and styling of the close button.
- `DrawerHeading` handles the styling of the drawer heading.

```ts
// tool_name.tsx

import { DrawerHeader, DrawerHeading } from "../components"

import { ToolbarTool } from "../types"

function ToolComponent({ closeDrawer }) {
  return (
    <>
      <DrawerHeader closeDrawer={closeDrawer}>
        <DrawerHeading>Tool name</DrawerHeading>
      </DrawerHeader>
      {/* Tool drawer content implementation */}
    </>
  )
}

const ToolName: ToolbarTool = {
  component: ToolComponent,
  icon: "general.toolbarIconName",
  name: "Tool name",
}

export { ToolName }
```

## Migrate

These migration docs will guide you thru the process of updating to recent major releases.

- [Migrate to v6](./migration/v6.md)
- [Migrate to v5](./migration/v5.md)

## Changelog

This is a limited history.
Topbar releases often require a coordinated effort across products and no app can stay an outdated version for very long.
So, this CHANGELOG is a little lazy.

[CHANGELOG](./CHANGELOG.md)

## Story

If you like history, here are the initial release notes with technical considerations and trade-offs.

[STORY](./docs/STORY.md)

## Example product implementations

- [API](https://github.com/planningcenter/api/blob/master/app/javascript/components/api_topbar.jsx)
- [Accounts](https://github.com/planningcenter/accounts/blob/master/app/javascript/components/accounts_topbar.jsx)
- [Calendar](https://github.com/planningcenter/calendar/blob/master/app/javascript/components/CalendarTopbar.jsx)
- [Check-Ins](https://github.com/planningcenter/check-ins/blob/master/app/javascript/components/checkins_topbar.js)
- [Giving](https://github.com/planningcenter/giving/blob/master/app/javascript/application/components/giving_topbar.jsx)
- [Groups](https://github.com/planningcenter/groups/blob/master/app/javascript/application/components/groups_topbar.js)
- [People](https://github.com/planningcenter/people/blob/master/app/javascript/components/people_topbar.jsx)
- [Publishing](https://github.com/planningcenter/publishing/blob/master/app/javascript/src/PublishingTopbar.js)
- [Registrations](https://github.com/planningcenter/registrations/blob/master/app/javascript/components/registrations_topbar.jsx)
- [Services](https://github.com/planningcenter/services/blob/master/app/javascript/components/ServicesTopbar.js)
