{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "active-sessions",
  "title": "Active Sessions",
  "description": "Display and manage all active sessions for the current user with revoke controls. Shows each session's browser, OS, and creation time.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "better-auth",
    "bowser",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "item",
    "skeleton",
    "sonner",
    "spinner"
  ],
  "files": [
    {
      "path": "src/components/auth/settings/security/active-sessions.tsx",
      "content": "\"use client\"\n\nimport { useAuth, useListSessions, useSession } from \"@better-auth-ui/react\"\nimport { Fragment } from \"react\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport {\n  Item,\n  ItemContent,\n  ItemGroup,\n  ItemMedia,\n  ItemSeparator\n} from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"@/lib/utils\"\nimport { ActiveSession } from \"./active-session\"\n\nexport type ActiveSessionsProps = {\n  className?: string\n}\n\n/**\n * Render a card listing all active sessions for the current user with revoke controls.\n *\n * Shows each session's browser, OS, IP address, and creation time. The current session is marked\n * and navigates to sign-out on click, while other sessions can be revoked individually.\n *\n * @returns A JSX element containing the sessions card\n */\nexport function ActiveSessions({ className }: ActiveSessionsProps) {\n  const { authClient, localization } = useAuth()\n  const { data: session } = useSession(authClient)\n\n  const { data: sessions, isPending } = useListSessions(authClient)\n\n  const activeSessions = [...(sessions ?? [])].sort((activeSession) =>\n    activeSession.id === session?.session.id ? -1 : 1\n  )\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {localization.settings.activeSessions}\n      </h2>\n\n      <Card className={cn(\"p-0\", className)}>\n        <CardContent className=\"p-0\">\n          {isPending ? (\n            <SessionRowSkeleton />\n          ) : (\n            <ItemGroup className=\"gap-0\">\n              {activeSessions?.map((activeSession, index) => (\n                <Fragment key={activeSession.id}>\n                  {index > 0 && <ItemSeparator />}\n                  <ActiveSession activeSession={activeSession} />\n                </Fragment>\n              ))}\n            </ItemGroup>\n          )}\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n\nfunction SessionRowSkeleton() {\n  return (\n    <Item>\n      <ItemMedia>\n        <Skeleton className=\"size-10 rounded-md\" />\n      </ItemMedia>\n      <ItemContent>\n        <Skeleton className=\"h-4 w-20\" />\n        <Skeleton className=\"h-3 w-32\" />\n      </ItemContent>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/active-sessions.tsx"
    },
    {
      "path": "src/components/auth/settings/security/active-session.tsx",
      "content": "\"use client\"\n\nimport { useAuth, useRevokeSession, useSession } from \"@better-auth-ui/react\"\nimport type { Session } from \"better-auth\"\nimport Bowser from \"bowser\"\nimport { LogOut, Monitor, Smartphone, X } from \"lucide-react\"\nimport { toast } from \"sonner\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemMedia,\n  ItemTitle\n} from \"@/components/ui/item\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\nfunction timeAgo(date: Date) {\n  const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n  const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: \"auto\" })\n\n  const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n    [\"year\", 31536000],\n    [\"month\", 2592000],\n    [\"week\", 604800],\n    [\"day\", 86400],\n    [\"hour\", 3600],\n    [\"minute\", 60],\n    [\"second\", 1]\n  ]\n\n  for (const [unit, threshold] of UNITS) {\n    if (seconds >= threshold) {\n      return rtf.format(-Math.floor(seconds / threshold), unit)\n    }\n  }\n\n  return rtf.format(0, \"second\")\n}\n\nexport type ActiveSessionProps = {\n  activeSession: Session\n}\n\n/**\n * Render a single active session row with device info and revoke control.\n *\n * Shows the session's browser, OS, and creation time. The current session is marked\n * and navigates to sign-out on click, while other sessions can be revoked individually.\n *\n * @param session - The session object containing id, token, userAgent, ipAddress, and createdAt\n * @returns A JSX element containing the active session row\n */\nexport function ActiveSession({ activeSession }: ActiveSessionProps) {\n  const { authClient, basePaths, localization, viewPaths, navigate } = useAuth()\n  const { data: session } = useSession(authClient, { refetchOnMount: false })\n\n  const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(\n    authClient,\n    {\n      onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)\n    }\n  )\n\n  const isCurrentSession = activeSession.token === session?.session.token\n  const ua = Bowser.parse(activeSession.userAgent || \"\")\n  const isMobile =\n    ua.platform.type === \"mobile\" || ua.platform.type === \"tablet\"\n\n  return (\n    <Item>\n      <ItemMedia variant=\"icon\">\n        {isMobile ? <Smartphone /> : <Monitor />}\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle>\n          {ua.browser.name || \"Unknown Browser\"}\n          {ua.os.name ? `, ${ua.os.name}` : \"\"}\n        </ItemTitle>\n        {isCurrentSession ? (\n          <Badge variant=\"secondary\">\n            {localization.settings.currentSession}\n          </Badge>\n        ) : (\n          activeSession.createdAt && (\n            <ItemDescription className=\"capitalize\">\n              {timeAgo(activeSession.createdAt)}\n            </ItemDescription>\n          )\n        )}\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() =>\n            isCurrentSession\n              ? navigate({\n                  to: `${basePaths.auth}/${viewPaths.auth.signOut}`\n                })\n              : revokeSession(activeSession)\n          }\n          disabled={isRevoking}\n          aria-label={\n            isCurrentSession\n              ? localization.auth.signOut\n              : localization.settings.revokeSession\n          }\n        >\n          {isRevoking ? <Spinner /> : isCurrentSession ? <LogOut /> : <X />}\n\n          {isCurrentSession\n            ? localization.auth.signOut\n            : localization.settings.revoke}\n        </Button>\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/active-session.tsx"
    }
  ],
  "type": "registry:component"
}