Benachrichtigungen

Der Bereich „Benachrichtigungen“ in den Einstellungen eines KI-Produkts. Ein Raster aus Kanal und Ereignis mit Toggles für Zeile, Spalte und alle, Ruhezeiten mit einer Live-Zeile zur nächsten Ruhephase, ein E-Mail-Digest, echte Testsendungen für Desktop, E-Mail, Push und Slack, Umgang mit Browser-Berechtigungen und ein Slack-Verbindungsablauf. Passt in jeden Settings-Bereich.

Jedes KI-Produkt landet bei derselben Benachrichtigungsseite: eine Liste dessen, was passieren kann, eine Liste der Orte, an denen man dich erreicht, und eine Möglichkeit, es nachts abzustellen. NotificationSettings ist diese Seite als ein Bereich. Er passt in eine SettingsSection und nutzt denselben Entwurf, dieselbe Speicherleiste und denselben Schutz vor ungespeicherten Änderungen wie der Rest der Settings.

Den Kern bildet ein Raster aus Ereignissen und Kanälen. Die oberste Zeile schaltet einen Kanal für jedes Ereignis ein oder aus, die erste Spalte schaltet ein Ereignis überall ein oder aus, und die Eckcheckbox erledigt alles. Jede zeigt einen Strich, wenn nur einige aktiv sind. Per Tastatur ist das Raster ein Tab-Stopp: Pfeiltasten wechseln zwischen Checkboxen, Pos1 und Ende springen entlang einer Zeile, und die Leertaste schaltet um. Screenreader hören jede als „E-Mail für Agent finished“. Auf einem Smartphone wird das Raster zu einer Card pro Ereignis mit großen Kanal-Chips.

Jeder Kanal hat ein Send test, das wirklich sendet. Desktop und Push zeigen ein Benachrichtigungsbanner über der Seite, und Desktop löst außerdem eine echte Systembenachrichtigung aus, sobald der Browser es erlaubt. E-Mail öffnet eine Vorschau der Nachricht in einem Posteingang, bei gebündelten E-Mails die Digest-Version. Slack zeigt die Nachricht so, wie dein Team sie im Kanal sieht.

Desktop liest die Berechtigung des Browsers. Wurde noch nicht gefragt, fragt Allow; ist sie blockiert, sagt es, wo man sie ändert; kann der Browser keine Benachrichtigungen zeigen, sagt es das. Slack startet getrennt: Seine Spalte ist aus, und Connect öffnet einen Dialog zur Wahl von Workspace und Kanal. Schlägt das Verbinden fehl, erscheint der Grund im Dialog, und nichts geht verloren.

Ruhezeiten halten Desktop- und Push-Hinweise zwischen zwei Uhrzeiten an den gewählten Tagen zurück, in der gewählten Zeitzone, mit einer Zeile, die sagt, wann die nächste Ruhephase beginnt oder dass gerade Ruhe ist. Dringende Ereignisse wie Freigaben können weiterhin durchkommen.

  1. Die Pro-Registry zu components.json hinzufügen

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token hinzufügen

    Erstelle auf deiner Kontoseite einen Token und trage ihn in .env.local als HEXTAUI_PRO_TOKEN ein.

  3. Den Block hinzufügen

    pnpm dlx shadcn@latest add @hextaui-pro/notifications

An deine API anbinden

Übergib die gespeicherten Einstellungen und ein asynchrones onSave. Änderungen bleiben ein Entwurf, bis jemand in der Speicherleiste oder mit ⌘S speichert. Gib Feldfehler zurück, um sie unter dem Feld zu zeigen, oder wirf einen Fehler, um die Meldung in der Speicherleiste zu zeigen. onSendTest sendet einen echten Test, bevor die Vorschau erscheint.

"use client"

import { IconBell } from "@tabler/icons-react"

import { SettingsSection, SettingsShell } from "../settings/settings"

import { NotificationSettings, type NotificationPreferences } from "@/components/blocks/notifications/notification-settings"

export function NotificationsPage({ preferences }: { preferences: NotificationPreferences }) {
  return (
    <SettingsShell
      sections={[{ id: "notifications", label: "Notifications", icon: <IconBell /> }]}
      className="h-svh"
    >
      <SettingsSection id="notifications">
        <NotificationSettings
          preferences={preferences}
          email="[email protected]"
          device="Mia’s iPhone"
          onSave={async (values) => {
            const response = await fetch("/api/notifications", {
              method: "PUT",
              body: JSON.stringify(values),
            })
            if (response.status === 422) return { quietDays: "Pick at least one day." }
            if (!response.ok) throw new Error("Check your connection and try again.")
          }}
          onSendTest={async ({ channel, event }) => {
            const response = await fetch("/api/notifications/test", {
              method: "POST",
              body: JSON.stringify({ channel, event: event.id }),
            })
            if (!response.ok) throw new Error("Couldn’t send the test.")
          }}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Deine eigenen Ereignisse und Kanäle

Übergib events, um Zeilen hinzuzufügen oder zu entfernen, und channels, um Spalten zu wählen. Jedes Ereignis kann ein sample für Testvorschauen und urgent tragen, um durch die Ruhezeiten zu kommen. onSlackConnect erhält den gewählten Workspace und Kanal; wirf einen Fehler, um zu zeigen, warum es fehlschlug, und der Dialog bleibt offen.

"use client"

import * as React from "react"

import {
  NotificationSettings,
  notificationEvents,
  type NotificationEvent,
  type NotificationPreferences,
  type SlackConnection,
  type SlackWorkspace,
} from "@/components/blocks/notifications/notification-settings"

const events: NotificationEvent[] = [
  ...notificationEvents.filter((event) => event.id !== "billing"),
  {
    id: "deploys",
    label: "Deploys",
    description: "An agent ships to production.",
    sample: {
      title: "Deployed to production",
      body: "“Fix flaky checkout test” is live. 2 checks passed.",
      subject: "Deployed: Fix flaky checkout test",
      action: "Open deploy",
    },
  },
]

export function TeamNotifications({
  preferences,
  workspaces,
  connection,
}: {
  preferences: NotificationPreferences
  workspaces: SlackWorkspace[]
  connection: SlackConnection | null
}) {
  const [slack, setSlack] = React.useState(connection)

  return (
    <NotificationSettings
      preferences={preferences}
      events={events}
      channels={["email", "slack"]}
      email="[email protected]"
      slack={slack}
      slackWorkspaces={workspaces}
      onSlackConnect={async ({ workspace, channel }) => {
        const response = await fetch("/api/slack/connect", {
          method: "POST",
          body: JSON.stringify({ workspace: workspace.id, channel: channel.id }),
        })
        if (response.status === 403) {
          throw new Error(`Hexta isn’t in #${channel.name}. Run /invite @Hexta there first.`)
        }
        if (!response.ok) throw new Error("Slack didn’t answer. Try again.")
        setSlack({ workspace: workspace.name, channel: channel.name })
      }}
      onSlackDisconnect={async () => {
        await fetch("/api/slack/connect", { method: "DELETE" })
        setSlack(null)
      }}
      onSave={(values) =>
        fetch("/api/notifications", { method: "PUT", body: JSON.stringify(values) }).then(() => {})
      }
    />
  )
}

Aufbau

Die Teile, die du zusammensetzt, von außen nach innen.

PartBeschreibung
NotificationSettingsDer ganze Bereich: das Ereignisraster, Kanäle mit Tests, Ruhezeiten und der E-Mail-Digest.
notificationEventsDie Standardereignisse: Agent finished, Needs approval, Run failed, Mentions, Weekly summary und Billing.
notificationChannelsDie Standardkanäle: desktop, email, push und slack.

NotificationSettings

Muss innerhalb einer SettingsSection stehen.

PropTypStandard
preferencesDas jetzt Gespeicherte: { matrix, quietHours, quietFrom, quietTo, quietDays, timeZone, quietUrgent, digest }. matrix ordnet jeder Ereignis-ID die aktiven Kanäle zu. Ändert sie sich und gibt es keine Änderungen, folgt der Entwurf ihr.
NotificationPreferences–
onSaveSpeichert den Entwurf. Gib { quietDays: "…" } und Ähnliches zurück, um Feldfehler zu zeigen, oder wirf einen Fehler, um die Meldung in der Speicherleiste zu zeigen.
(preferences) => void | errors | Promise<void | errors>–
eventsZeilen des Rasters: { id, label, description?, urgent?, sample? }. sample ist { title, body, subject, action } für Testvorschauen. urgent-Ereignisse können durch Ruhezeiten kommen.
NotificationEvent[]notificationEvents
channelsSpalten des Rasters und Zeilen unter Channels, der Reihe nach. Beliebige von "desktop", "email", "push" und "slack".
NotificationChannel[]notificationChannels
emailWohin E-Mails gehen. Wird unter Email und in der Test-E-Mail angezeigt.
string"[email protected]"
deviceDas Smartphone, das Push-Benachrichtigungen erhält.
string"your phone"
slackDer verbundene Slack-Kanal. Solange er null ist, ist die Slack-Spalte aus.
{ workspace, channel } | nullnull
slackWorkspacesWorkspaces zur Auswahl beim Verbinden: { id, name, channels: { id, name, private? }[] }.
SlackWorkspace[][]
onSlackConnectVerbindet den gewählten Kanal. Wirf einen Fehler, um die Meldung im Dialog zu zeigen.
({ workspace, channel }) => void | Promise<void>–
onSlackDisconnectTrennt Slack, nachdem die Person bestätigt hat.
() => void | Promise<void>–
onSendTestSendet einen echten Test. Die Vorschau erscheint, sobald er aufgelöst ist, und der Button bietet einen Wiederholungsversuch an, wenn er einen Fehler wirft. event ist das erste, das für diesen Kanal aktiv ist.
({ channel, event }) => void | Promise<void>–
TasteAktion
TabSpringt einmal ins Raster, zur zuletzt verwendeten Checkbox, dann weiter zu den Kanälen.
↑↓←→Wechselt im Raster zwischen Checkboxen. Links und rechts folgen der Leserichtung.
HomeEndSpringt im Raster zum Anfang oder Ende der Zeile. Mit Strg oder ⌘ zur ersten oder letzten Checkbox.
SpaceSchaltet die fokussierte Checkbox ein oder aus. Schaltet bei einer Zeilen-, Spalten- oder Eckcheckbox alle ein, oder aus, wenn alle an sind.
EscSchließt ein Test-Benachrichtigungsbanner, solange es den Fokus hat.
  • Das Raster ist ein ARIA-Grid mit Spalten- und Zeilenköpfen, und jede Checkbox trägt den Namen ihres Kanals und Ereignisses, etwa „Mobile Push für Run failed“.
  • Checkboxen für Zeile, Spalte und Ecke sind gemischt, wenn nur einige aktiv sind, sodass Screenreader „teilweise ausgewählt“ hören.
  • Testsendungen, Berechtigungsergebnisse und Slack-Änderungen werden höflich angesagt. Die nächste Ruhephase ist eine Statuszeile, die sich beim Bearbeiten aktualisiert.
  • Test-Banner pausieren bei Hover oder Fokus, haben einen Schließen-Button und blenden bei reduzierter Bewegung aus, statt zu gleiten.
  • Auf Smartphones ist jeder Kanal-Chip ein 44-px-Ziel.

Gebaut mit

Die kostenlosen HextaUI-Komponenten, aus denen Notifications besteht. Jede lässt sich einzeln installieren.

Code

5 Dateien, hinzugefügt zu components/blocks/notifications.