# Help Center

URL: https://developers.uidu.org/docs/domains/help-center

> Fetch help center channels, KB collections, and articles.

* **Use it for:** [Public website](https://developers.uidu.org/docs/paths/public-website.md) · [Server integration](https://developers.uidu.org/docs/paths/server-integration.md)
* **Reads:** public token — works in the browser
* **Writes:** Bearer (`apiKey`) — server-side only
* **CLI:** `uidu channel`
* **Auth:** what each token can do and where it may run — [Authentication](https://developers.uidu.org/docs/foundations/authentication.md)

The help center is a workspace's self-service support site: **channels** of knowledge-base
articles, grouped by topic. `getChannel` reads a whole channel — its collections and their
articles — in one request, which is usually all a help-center front end needs.

## Mental model

```
Workspace
└── Channel                 { id, name, isDefault } — the help center itself
    ├── kbCollections       topics: { id, name, description, iconData }
    │   └── kbArticles      { id, name, body, updatedAt, workspaceTagList }
    └── channelKbCollections   { position, kbCollection { id } } — the order inside this channel
```

* **Which channel.** A workspace can have several. `listChannels` marks the main one with
  `isDefault`: code that has no channel id configured should prefer it over the first row.
* **One request, capped.** `getChannel` fetches the **last 100 collections** and the **last 100
  articles** of each. Past that, read collections and articles directly with the
  [Knowledge base](https://developers.uidu.org/docs/domains/kb.md) functions, which paginate.
* **Order.** Articles inside a collection come back ordered by their `position`. A collection's
  position in *this* channel lives in `channelKbCollections` (by `kbCollection.id`), not on the
  collection — sort with it when the order matters.
* **Drafts are left out** server-side: every article you get here is published.
* **Field shapes.** A collection's `name` and `description` are JSON — usually a string, possibly a
  localization map like `{ en, it }`; `iconData` is icon metadata (JSON). An article's `name` is a
  string, its `body` is rich-text JSON (render it with
  [`<RichText>`](https://developers.uidu.org/docs/tools/react/components.md)), and `workspaceTagList` is its tags,
  `{ id, name, color }`.
* **Search.** Knowledge-base articles aren't in the workspace search index, so
  [`search`](https://developers.uidu.org/docs/domains/search.md) won't find them: filter the channel's articles in memory.
* **Writes.** `createChannel`, `updateChannel` and `deleteChannel` need the Bearer client and return
  their payload with `errors` (the new channel is at `channel.node`). Collections and articles are
  written through the [Knowledge base](https://developers.uidu.org/docs/domains/kb.md) functions.

## Use cases

### Help center home

Goal: a `/help` page that shows the default channel's topics in the channel's own order, each with
its articles.

```tsx
// src/lib/help.ts
import { cache } from 'react';
import { getChannel, listChannels } from '@uidu/client';
import { uidu } from './uidu';

export const getHelpChannel = cache(async () => {
  const channels = await listChannels(uidu, { first: 25 });
  const main = channels.find((c) => c.isDefault) ?? channels[0];
  return main ? getChannel(uidu, { id: main.id }) : null;
});

// A collection's name/description may be a plain string or a { en, it, … } map.
export function label(value: unknown, locale = 'en'): string {
  if (typeof value === 'string') return value;
  if (value && typeof value === 'object') {
    const map = value as Record<string, unknown>;
    return String(map[locale] ?? Object.values(map)[0] ?? '');
  }
  return '';
}

// src/app/help/page.tsx
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { getHelpChannel, label } from '@/lib/help';

export default async function HelpHomePage() {
  const channel = await getHelpChannel();
  if (!channel) notFound();

  const position = new Map(
    (channel.channelKbCollections?.edges ?? []).flatMap((e) =>
      e?.node ? [[e.node.kbCollection.id, e.node.position] as const] : [],
    ),
  );
  const collections = (channel.kbCollections?.edges ?? [])
    .flatMap((e) => (e?.node ? [e.node] : []))
    .sort((a, b) => (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0));

  return (
    <div>
      <h1>{channel.name}</h1>
      {collections.map((c) => (
        <section key={c.id}>
          <h2>{label(c.name)}</h2>
          <p>{label(c.description)}</p>
          <ul>
            {(c.kbArticles?.edges ?? []).map(
              (a) =>
                a?.node && (
                  <li key={a.node.id}>
                    <Link href={`/help/${encodeURIComponent(a.node.id)}`}>{a.node.name}</Link>
                  </li>
                ),
            )}
          </ul>
        </section>
      ))}
    </div>
  );
}
```

* Set the channel id in an env var instead of `listChannels` if you want to pin one: ids are
  GlobalIDs, so they belong to one workspace and don't travel between workspaces.
* `cache()` lets the home and the article page share one `getChannel` per render.

### Article page

Goal: `/help/[articleId]` renders one article with its body, under its topic.

```tsx
// src/app/help/[articleId]/page.tsx
import { notFound } from 'next/navigation';
import { getKbArticle } from '@uidu/client';
import { RichText } from '@uidu/react';
import { label } from '@/lib/help';
import { uidu } from '@/lib/uidu';

export default async function ArticlePage({
  params,
}: {
  params: Promise<{ articleId: string }>;
}) {
  const { articleId } = await params;
  const article = await getKbArticle(uidu, { id: decodeURIComponent(articleId) });
  if (!article) notFound();

  return (
    <article>
      {article.kbCollection && <p>{label(article.kbCollection.name)}</p>}
      <h1>{article.name}</h1>
      <RichText doc={article.body} />
      <small>Updated {new Date(article.updatedAt as string).toLocaleDateString()}</small>
    </article>
  );
}
```

* `getKbArticle` fetches the one article instead of the whole channel. If you already hold the
  channel (from `getHelpChannel()`), the article's `body` is in it too — look it up there and save
  a request.
* `getKbArticle` doesn't check which channel the article belongs to. If you run several channels
  and must keep them apart, find the article inside the channel instead.

### Filter articles on the page

Goal: a quick filter over the help center, by text or by tag, without a server round trip.

```tsx
// src/app/help/article-filter.tsx
'use client';
import { useState } from 'react';
import Link from 'next/link';

type Item = { id: string; name: string | null; tags: string[] };

export function ArticleFilter({ items }: { items: Item[] }) {
  const [q, setQ] = useState('');
  const needle = q.trim().toLowerCase();
  const hits = needle
    ? items.filter(
        (a) =>
          a.name?.toLowerCase().includes(needle) ||
          a.tags.some((t) => t.toLowerCase().includes(needle)),
      )
    : items;

  return (
    <>
      <input
        type="search"
        value={q}
        onChange={(e) => setQ(e.target.value)}
        aria-label="Filter articles"
      />
      <ul>
        {hits.map((a) => (
          <li key={a.id}>
            <Link href={`/help/${encodeURIComponent(a.id)}`}>{a.name}</Link>
          </li>
        ))}
      </ul>
    </>
  );
}

// In the Server Component, flatten the channel into plain props:
const items = collections.flatMap((c) =>
  (c.kbArticles?.edges ?? []).flatMap((e) => {
    const a = e?.node;
    if (!a) return [];
    const tags = (a.workspaceTagList ?? []).flatMap((t) => (t.name ? [t.name] : []));
    return [{ id: a.id, name: a.name ?? null, tags }];
  }),
);
// <ArticleFilter items={items} />
```

* Pass only what the filter needs: the article `body` is large and doesn't need to reach the
  browser.

## API at a glance

Every `@uidu/client` function in **Help center**, generated from the source — the [full reference](https://developers.uidu.org/docs/reference/client.md#help-center) has the rest of the SDK.

| Function                                                                                          | Kind  | Auth         | CLI              | Description                                                                                                                                                                                |
| ------------------------------------------------------------------------------------------------- | ----- | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`createChannel`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts)  | write | Bearer       | `channel create` | Create a help-center channel. Returns the payload (with `errors`) or null. The new channel is at `channel.node`.                                                                           |
| [`deleteChannel`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts)  | write | Bearer       | `channel delete` | Delete a help-center channel by `{ input: { id } }`. Returns the payload (with `errors`) or null.                                                                                          |
| [`getChannel`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/help-center.ts)   | read  | public token | `channel get`    | One help center channel with its collections and their articles in a single request (the last 100 of each), or null.                                                                       |
| [`listChannels`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/help-center.ts) | read  | public token | `channel list`   | The workspace's help center channels. `isDefault` marks the one the workspace treats as its main channel — a caller that has no channel id configured should prefer it over the first row. |
| [`updateChannel`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts)  | write | Bearer       | `channel update` | Update a help-center channel: `{ input: { id, attributes } }`. Returns the payload (with `errors`) or null.                                                                                |

**Types:** `Channel`

**CLI:**

| Entity    | list | get | create | update | delete |
| --------- | :--: | :-: | :----: | :----: | :----: |
| `channel` |   ✓  |  ✓  |    ✓   |    ✓   |    ✓   |
