Notificações

A seção Notificações das configurações de um produto de IA. Uma grade de canal por evento com alternadores por linha, por coluna e geral, horário de silêncio com uma linha ao vivo do próximo silêncio, um resumo por e-mail, envios de teste reais para desktop, e-mail, push e Slack, tratamento da permissão do navegador e um fluxo de conexão com o Slack. Encaixa em qualquer seção de Configurações.

Todo produto de IA acaba com a mesma página de notificações: uma lista de coisas que podem acontecer, uma lista de lugares onde elas podem chegar até você e uma forma de fazer tudo parar à noite. NotificationSettings é essa página como uma única seção. Ela se encaixa em um SettingsSection e usa o mesmo rascunho, a mesma barra de salvar e a mesma proteção contra alterações não salvas do resto do Settings.

O núcleo é uma grade de eventos por canais. A linha de cima liga ou desliga um canal para todos os eventos, a primeira coluna liga ou desliga um evento em todos os canais, e a caixa de seleção do canto faz tudo isso. Cada uma mostra um traço quando apenas algumas estão ligadas. Pelo teclado, a grade é uma única parada de Tab: as setas movem entre as caixas de seleção, Home e End saltam ao longo de uma linha e Space alterna. Os leitores de tela ouvem cada uma como "E-mail para Agent finished". No celular, a grade vira um card por evento com chips de canal grandes.

Cada canal tem um Send test que realmente envia. Desktop e push mostram um banner de notificação sobre a página, e o desktop também dispara uma notificação real do sistema assim que o navegador permite. O e-mail abre uma prévia da mensagem em uma caixa de entrada, na versão de resumo quando os e-mails são agrupados. O Slack mostra a mensagem como sua equipe a vê no canal.

Desktop lê a permissão do navegador. Quando ela ainda não foi solicitada, Allow a solicita; quando está bloqueada, informa onde alterá-la; quando o navegador não consegue mostrar notificações, diz isso. O Slack começa desconectado: sua coluna fica desligada, e Connect abre um diálogo para escolher um workspace e um canal. Se a conexão falhar, o motivo aparece no diálogo e nada se perde.

O horário de silêncio segura os alertas de desktop e push entre dois horários nos dias que você escolher, no fuso horário que você escolher, com uma linha que diz quando o próximo período de silêncio começa ou que está silencioso agora. Eventos urgentes, como aprovações, ainda podem passar.

  1. Adicione o registro Pro ao components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Adicione seu token

    Crie um token na sua página de conta e coloque-o em .env.local como HEXTAUI_PRO_TOKEN.

  3. Adicione o bloco

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

Conecte à sua API

Passe as preferências salvas e um onSave assíncrono. As edições ficam em um rascunho até alguém salvar pela barra de salvar ou com ⌘S. Retorne erros de campo para exibi-los sob o campo, ou lance um erro para mostrar a mensagem na barra de salvar. onSendTest envia um teste real antes de a prévia aparecer.

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

Seus próprios eventos e canais

Passe events para adicionar ou remover linhas e channels para escolher colunas. Cada evento pode levar um sample para as prévias de teste e urgent para deixá-lo passar durante o horário de silêncio. onSlackConnect recebe o workspace e o canal escolhidos; lance um erro para mostrar por que falhou, e o diálogo permanece aberto.

"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(() => {})
      }
    />
  )
}

Anatomia

As partes que você compõe, de fora para dentro.

ParteDescrição
NotificationSettingsA seção inteira: a grade de eventos, os canais com testes, o horário de silêncio e o resumo por e-mail.
notificationEventsOs eventos padrão: Agent finished, Needs approval, Run failed, Mentions, Weekly summary e Billing.
notificationChannelsOs canais padrão: desktop, email, push e slack.

NotificationSettings

Deve estar dentro de um SettingsSection.

PropTipoPadrão
preferencesO que está salvo agora: { matrix, quietHours, quietFrom, quietTo, quietDays, timeZone, quietUrgent, digest }. matrix mapeia o id de cada evento para os canais que estão ligados. Quando ela muda e não há edições, o rascunho a acompanha.
NotificationPreferences–
onSaveSalva o rascunho. Retorne { quietDays: "…" } e similares para mostrar erros de campo, ou lance um erro para mostrar a mensagem na barra de salvar.
(preferences) => void | errors | Promise<void | errors>–
eventsLinhas da grade: { id, label, description?, urgent?, sample? }. sample é { title, body, subject, action } para as prévias de teste. Eventos urgent podem passar durante o horário de silêncio.
NotificationEvent[]notificationEvents
channelsColunas da grade e linhas em Channels, em ordem. Qualquer um entre "desktop", "email", "push" e "slack".
NotificationChannel[]notificationChannels
emailPara onde vai o e-mail. Exibido em Email e no e-mail de teste.
string"[email protected]"
deviceO celular que recebe as notificações push.
string"your phone"
slackO canal conectado do Slack. Enquanto for null, a coluna do Slack fica desligada.
{ workspace, channel } | nullnull
slackWorkspacesWorkspaces para escolher ao conectar: { id, name, channels: { id, name, private? }[] }.
SlackWorkspace[][]
onSlackConnectConecta o canal escolhido. Lance um erro para mostrar a mensagem no diálogo.
({ workspace, channel }) => void | Promise<void>–
onSlackDisconnectDesconecta o Slack depois que a pessoa confirma.
() => void | Promise<void>–
onSendTestEnvia um teste real. A prévia aparece quando ele é resolvido, e o botão oferece uma nova tentativa se lançar um erro. event é o primeiro que está ligado para aquele canal.
({ channel, event }) => void | Promise<void>–
TeclaAção
TabEntra na grade uma vez, na última caixa de seleção que você usou, e depois segue para os canais.
↑↓←→Na grade, move entre as caixas de seleção. Esquerda e direita seguem a direção de leitura.
HomeEndNa grade, salta para o início ou o fim da linha. Com Ctrl ou ⌘, para a primeira ou a última caixa de seleção.
SpaceLiga ou desliga a caixa de seleção em foco. Em uma caixa de linha, coluna ou canto, liga todas, ou desliga quando todas estão ligadas.
EscDispensa um banner de notificação de teste enquanto ele tem o foco.
  • A grade é uma grid ARIA com cabeçalhos de coluna e de linha, e cada caixa de seleção é nomeada por seu canal e evento, como "Push no celular para Run failed".
  • As caixas de seleção de linha, coluna e canto ficam mistas quando apenas algumas estão ligadas, então os leitores de tela anunciam "parcialmente marcada".
  • Os envios de teste, os resultados de permissão e as mudanças no Slack são anunciados de forma polite. O próximo período de silêncio é uma linha de status que se atualiza conforme você edita.
  • Os banners de teste pausam enquanto estão com hover ou foco, têm um botão de dispensar e somem com fade em vez de deslizar com movimento reduzido.
  • No celular, todo chip de canal é um alvo de 44px.

Construído com

Os componentes gratuitos do HextaUI de que Notifications é feito. Cada um é instalado separadamente.

Código

5 arquivos, adicionados a components/blocks/notifications.