-

Channel Status Card

PreviousNext

A status card for web chat, email, and messaging channel integrations.

Installation

pnpm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/channel-status-card.json
npm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/channel-status-card.json
yarn
yarn dlx shadcn@latest add https://ui.zgi.ai/r/channel-status-card.json

Component

components/ui/channel-status-card.tsx
"use client"

import * as React from "react"
import {
  AlertCircleIcon,
  CheckCircle2Icon,
  MessageCircleIcon,
  RefreshCcwIcon,
} from "lucide-react"

import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"

export type ChannelStatus = "connected" | "syncing" | "error" | "disabled"

export interface ChannelStatusCardProps
  extends Omit<React.ComponentProps<typeof Card>, "title"> {
  name: React.ReactNode
  provider?: React.ReactNode
  status?: ChannelStatus
  description?: React.ReactNode
  icon?: React.ReactNode
  conversations?: number
  messages?: number
  lastEvent?: React.ReactNode
  onSync?: () => void
  onManage?: () => void
}

const channelStatusConfig = {
  connected: {
    label: "Connected",
    icon: CheckCircle2Icon,
    className: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
  },
  syncing: {
    label: "Syncing",
    icon: RefreshCcwIcon,
    className: "bg-blue-500/10 text-blue-700 dark:text-blue-300",
  },
  error: {
    label: "Error",
    icon: AlertCircleIcon,
    className: "bg-destructive/10 text-destructive",
  },
  disabled: {
    label: "Disabled",
    icon: MessageCircleIcon,
    className: "bg-muted text-muted-foreground",
  },
} satisfies Record<
  ChannelStatus,
  {
    label: string
    icon: React.ComponentType<{ className?: string }>
    className: string
  }
>

export function ChannelStatusCard({
  name,
  provider,
  status = "connected",
  description,
  icon,
  conversations,
  messages,
  lastEvent,
  onSync,
  onManage,
  className,
  ...props
}: ChannelStatusCardProps) {
  const config = channelStatusConfig[status]
  const StatusIcon = config.icon

  return (
    <Card className={cn("rounded-lg py-0", className)} {...props}>
      <CardContent className="space-y-4 p-4">
        <div className="flex items-start gap-3">
          <div className="bg-muted text-muted-foreground flex size-10 shrink-0 items-center justify-center rounded-lg">
            {icon ?? <MessageCircleIcon className="size-5" />}
          </div>
          <div className="min-w-0 flex-1">
            <div className="flex min-w-0 flex-wrap items-center gap-2">
              <h3 className="truncate text-sm font-medium">{name}</h3>
              {provider ? (
                <Badge variant="outline" className="h-6 rounded-md">
                  {provider}
                </Badge>
              ) : null}
              <Badge
                variant="secondary"
                className={cn("h-6 rounded-md border-0", config.className)}
              >
                <StatusIcon
                  className={cn(
                    "size-3.5",
                    status === "syncing" && "animate-spin"
                  )}
                />
                {config.label}
              </Badge>
            </div>
            {description ? (
              <p className="text-muted-foreground mt-1 line-clamp-2 text-xs leading-5">
                {description}
              </p>
            ) : null}
          </div>
        </div>

        <div className="grid grid-cols-2 gap-2 text-xs">
          {typeof conversations === "number" ? (
            <div className="rounded-md border p-2">
              <div className="text-muted-foreground">Conversations</div>
              <div className="mt-1 font-medium tabular-nums">
                {conversations.toLocaleString()}
              </div>
            </div>
          ) : null}
          {typeof messages === "number" ? (
            <div className="rounded-md border p-2">
              <div className="text-muted-foreground">Messages</div>
              <div className="mt-1 font-medium tabular-nums">
                {messages.toLocaleString()}
              </div>
            </div>
          ) : null}
        </div>

        <div className="flex flex-wrap items-center justify-between gap-3 border-t pt-3">
          {lastEvent ? (
            <span className="text-muted-foreground text-xs">{lastEvent}</span>
          ) : (
            <span />
          )}
          <div className="flex gap-2">
            {onSync ? (
              <Button
                type="button"
                variant="outline"
                size="sm"
                className="h-8 gap-2 rounded-md"
                onClick={onSync}
              >
                <RefreshCcwIcon className="size-3.5" />
                Sync
              </Button>
            ) : null}
            {onManage ? (
              <Button
                type="button"
                size="sm"
                className="h-8 rounded-md"
                onClick={onManage}
              >
                Manage
              </Button>
            ) : null}
          </div>
        </div>
      </CardContent>
    </Card>
  )
}

Usage

import { ChannelStatusCard } from "@/components/ui/channel-status-card"
 
export function Example() {
  return (
    <ChannelStatusCard
      name="Website Chat"
      provider="Web"
      status="connected"
      conversations={1240}
      messages={18420}
    />
  )
}

Props

PropTypeDefault
nameReactNoderequired
providerReactNode-
status"connected" | "syncing" | "error" | "disabled""connected"
descriptionReactNode-
iconReactNode-
conversationsnumber-
messagesnumber-
lastEventReactNode-
onSync() => void-
onManage() => void-