{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-session-manager",
  "title": "Auth Session Manager",
  "description": "Manage active sessions across devices with revoke functionality.",
  "registryDependencies": [
    "alert-dialog",
    "badge",
    "button",
    "card",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-session-manager.tsx",
      "content": "\"use client\";\n\nimport {\n  AlertTriangle,\n  Globe,\n  Loader2,\n  LogOut,\n  Monitor,\n  Smartphone,\n  Tablet,\n} from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/registry/new-york/ui/alert-dialog\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport type DeviceType = \"desktop\" | \"mobile\" | \"tablet\" | \"unknown\";\n\nexport interface Session {\n  id: string;\n  deviceName: string;\n  deviceType: DeviceType;\n  browser?: string;\n  os?: string;\n  ipAddress?: string;\n  location?: string;\n  lastActive: Date;\n  isCurrent: boolean;\n}\n\nexport interface AuthSessionManagerProps {\n  sessions?: Session[];\n  onRevoke?: (sessionId: string) => void;\n  onRevokeAll?: () => void;\n  className?: string;\n  isLoading?: boolean;\n  errors?: {\n    general?: string;\n  };\n}\n\nconst DEVICE_ICONS: Record<\n  DeviceType,\n  React.ComponentType<{ className?: string }>\n> = {\n  desktop: Monitor,\n  mobile: Smartphone,\n  tablet: Tablet,\n  unknown: Globe,\n};\n\nfunction formatDate(date: Date): string {\n  const year = date.getFullYear();\n  const month = date.toLocaleString(\"en-US\", { month: \"short\" });\n  const day = date.getDate();\n  return `${month} ${day}, ${year}`;\n}\n\nfunction formatLastActive(date: Date): string {\n  const now = new Date();\n  const diff = now.getTime() - date.getTime();\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(diff / 3_600_000);\n  const days = Math.floor(diff / 86_400_000);\n\n  if (minutes < 1) return \"Just now\";\n  if (minutes < 60) return `${minutes}m ago`;\n  if (hours < 24) return `${hours}h ago`;\n  if (days === 1) return \"Yesterday\";\n  if (days < 7) return `${days}d ago`;\n  return formatDate(date);\n}\n\ninterface ErrorAlertProps {\n  message: string;\n}\n\nfunction ErrorAlert({ message }: ErrorAlertProps) {\n  return (\n    <div\n      aria-live=\"polite\"\n      className=\"flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm\"\n      role=\"alert\"\n    >\n      <AlertTriangle aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n      <span>{message}</span>\n    </div>\n  );\n}\n\ninterface EmptyStateProps {\n  message?: string;\n}\n\nfunction EmptyState({ message = \"No active sessions found\" }: EmptyStateProps) {\n  return (\n    <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n      <p className=\"text-muted-foreground text-sm\">{message}</p>\n    </div>\n  );\n}\n\ninterface RevokeSessionDialogProps {\n  deviceName: string;\n  isLoading: boolean;\n  onRevoke: (sessionId: string) => void;\n  sessionId: string;\n}\n\nfunction RevokeSessionDialog({\n  deviceName,\n  isLoading,\n  onRevoke,\n  sessionId,\n}: RevokeSessionDialogProps) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>\n        <Button\n          aria-label={`Revoke session on ${deviceName}`}\n          className=\"min-h-[32px] min-w-[32px] touch-manipulation\"\n          disabled={isLoading}\n          size=\"icon\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <LogOut aria-hidden=\"true\" className=\"size-4\" />\n        </Button>\n      </AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>Revoke session?</AlertDialogTitle>\n          <AlertDialogDescription>\n            This will sign out the session on {deviceName}. The user will need\n            to sign in again to access their account from this device.\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction\n            className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n            onClick={() => onRevoke(sessionId)}\n          >\n            Revoke session\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n\ninterface SessionItemProps {\n  isLoading: boolean;\n  onRevoke?: (sessionId: string) => void;\n  session: Session;\n}\n\nfunction SessionItem({ session, onRevoke, isLoading }: SessionItemProps) {\n  const DeviceIcon = DEVICE_ICONS[session.deviceType] || DEVICE_ICONS.unknown;\n\n  return (\n    <div className=\"flex flex-col gap-3 rounded-lg border bg-card p-4 sm:flex-row sm:items-start sm:gap-4\">\n      <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted\">\n        <DeviceIcon\n          aria-hidden=\"true\"\n          className=\"size-5 text-muted-foreground\"\n        />\n      </div>\n      <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <h4 className=\"font-medium text-sm\">{session.deviceName}</h4>\n          {session.isCurrent && (\n            <Badge className=\"text-xs\" variant=\"secondary\">\n              Current session\n            </Badge>\n          )}\n        </div>\n        <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground text-xs\">\n          {session.browser && (\n            <>\n              <span>{session.browser}</span>\n              {session.os && <span aria-hidden=\"true\">•</span>}\n            </>\n          )}\n          {session.os && <span>{session.os}</span>}\n          {(session.browser || session.os) && session.location && (\n            <span aria-hidden=\"true\">•</span>\n          )}\n          {session.location && <span>{session.location}</span>}\n        </div>\n        <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground text-xs\">\n          <span>Last active: {formatLastActive(session.lastActive)}</span>\n          {session.ipAddress && (\n            <>\n              <span aria-hidden=\"true\">•</span>\n              <span>IP: {session.ipAddress}</span>\n            </>\n          )}\n        </div>\n      </div>\n      {!session.isCurrent && onRevoke && (\n        <RevokeSessionDialog\n          deviceName={session.deviceName}\n          isLoading={isLoading}\n          onRevoke={onRevoke}\n          sessionId={session.id}\n        />\n      )}\n    </div>\n  );\n}\n\ninterface RevokeAllDialogProps {\n  isLoading: boolean;\n  onRevokeAll: () => void;\n  revokingAll: boolean;\n}\n\nfunction RevokeAllDialog({\n  isLoading,\n  onRevokeAll,\n  revokingAll,\n}: RevokeAllDialogProps) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>\n        <Button\n          aria-label=\"Revoke all other sessions\"\n          className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n          disabled={isLoading || revokingAll}\n          type=\"button\"\n          variant=\"outline\"\n        >\n          {revokingAll ? (\n            <>\n              <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n              Revoking…\n            </>\n          ) : (\n            <>\n              <LogOut aria-hidden=\"true\" className=\"size-4\" />\n              Revoke all\n            </>\n          )}\n        </Button>\n      </AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>Revoke all other sessions?</AlertDialogTitle>\n          <AlertDialogDescription>\n            This will sign out all other devices except this one. You will\n            remain signed in on this device, but all other active sessions will\n            be terminated.\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction\n            className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n            onClick={onRevokeAll}\n          >\n            Revoke all sessions\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n\ninterface CurrentSessionSectionProps {\n  isLoading: boolean;\n  onRevoke?: (sessionId: string) => void;\n  session: Session;\n}\n\nfunction CurrentSessionSection({\n  session,\n  onRevoke,\n  isLoading,\n}: CurrentSessionSectionProps) {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <h3 className=\"font-medium text-sm\">Current session</h3>\n      <SessionItem\n        isLoading={isLoading}\n        onRevoke={onRevoke}\n        session={session}\n      />\n    </div>\n  );\n}\n\ninterface OtherSessionsSectionProps {\n  isLoading: boolean;\n  onRevoke?: (sessionId: string) => void;\n  sessions: Session[];\n}\n\nfunction OtherSessionsSection({\n  sessions,\n  onRevoke,\n  isLoading,\n}: OtherSessionsSectionProps) {\n  if (sessions.length === 0) return null;\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <h3 className=\"font-medium text-sm\">\n        Other active sessions ({sessions.length})\n      </h3>\n      <div className=\"flex flex-col gap-3\">\n        {sessions.map((session) => (\n          <SessionItem\n            isLoading={isLoading}\n            key={session.id}\n            onRevoke={onRevoke}\n            session={session}\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nexport default function AuthSessionManager({\n  sessions = [],\n  onRevoke,\n  onRevokeAll,\n  className,\n  isLoading = false,\n  errors,\n}: AuthSessionManagerProps) {\n  const [revokingAll, setRevokingAll] = useState(false);\n\n  const handleRevokeAll = useCallback(async () => {\n    setRevokingAll(true);\n    try {\n      await onRevokeAll?.();\n    } finally {\n      setRevokingAll(false);\n    }\n  }, [onRevokeAll]);\n\n  const otherSessions = sessions.filter((s) => !s.isCurrent);\n  const currentSession = sessions.find((s) => s.isCurrent);\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div className=\"flex flex-col gap-2\">\n            <CardTitle>Active sessions</CardTitle>\n            <CardDescription>\n              Manage devices that are signed in to your account\n            </CardDescription>\n          </div>\n          {otherSessions.length > 0 && onRevokeAll && (\n            <RevokeAllDialog\n              isLoading={isLoading}\n              onRevokeAll={handleRevokeAll}\n              revokingAll={revokingAll}\n            />\n          )}\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {errors?.general && <ErrorAlert message={errors.general} />}\n\n          {sessions.length === 0 ? (\n            <EmptyState />\n          ) : (\n            <div className=\"flex flex-col gap-4\">\n              {currentSession && (\n                <>\n                  <CurrentSessionSection\n                    isLoading={isLoading}\n                    onRevoke={onRevoke}\n                    session={currentSession}\n                  />\n                  {otherSessions.length > 0 && <Separator />}\n                </>\n              )}\n\n              <OtherSessionsSection\n                isLoading={isLoading}\n                onRevoke={onRevoke}\n                sessions={otherSessions}\n              />\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}