{
  "$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/core@latest",
    "@better-auth-ui/react@latest",
    "better-auth",
    "bowser",
    "cn",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert",
    "alert-dialog",
    "badge",
    "button",
    "card",
    "item",
    "skeleton",
    "sonner",
    "spinner"
  ],
  "files": [
    {
      "path": "examples/start-shadcn-example/src/components/auth/reauthentication.tsx",
      "content": "\"use client\"\n\nimport {\n  getReauthenticationSignInURL,\n  isReauthenticationSignInURL\n} from \"@better-auth-ui/core\"\nimport { useAuth, useSignOut } from \"@better-auth-ui/react\"\nimport { useSyncExternalStore } from \"react\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\n\nconst subscribeToLocation = () => () => undefined\n\nfunction useIsReauthenticationSignIn() {\n  return useSyncExternalStore(\n    subscribeToLocation,\n    () => isReauthenticationSignInURL(new URL(window.location.href)),\n    () => false\n  )\n}\n\nexport type ReauthenticationActionProps = {\n  className?: string\n  showTitle?: boolean\n}\n\nexport function ReauthenticationAction({\n  className,\n  showTitle = true\n}: ReauthenticationActionProps) {\n  const auth = useAuth()\n  const signOut = useSignOut(auth.authClient)\n\n  const handleReauthentication = () => {\n    const signInURL = getReauthenticationSignInURL(\n      new URL(window.location.href),\n      `${auth.basePaths.auth}/${auth.viewPaths.auth.signIn}`\n    )\n    signOut.mutate(undefined, {\n      onSuccess: () => auth.navigate({ to: signInURL })\n    })\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-start gap-3 p-4\", className)}>\n      <div className=\"flex flex-col gap-1\">\n        {showTitle ? (\n          <h3 className=\"text-sm font-medium\">\n            {auth.localization.settings.reauthenticationTitle}\n          </h3>\n        ) : null}\n        <p className=\"text-sm text-muted-foreground\">\n          {auth.localization.settings.reauthenticationDescription}\n        </p>\n      </div>\n      <Button\n        disabled={signOut.isPending}\n        onClick={handleReauthentication}\n        size=\"sm\"\n      >\n        {signOut.isPending ? <Spinner data-icon=\"inline-start\" /> : null}\n        {auth.localization.settings.reauthenticationAction}\n      </Button>\n    </div>\n  )\n}\n\nexport function ReauthenticationNotice() {\n  const auth = useAuth()\n  const isReauthenticationSignIn = useIsReauthenticationSignIn()\n  if (!isReauthenticationSignIn) return null\n\n  return (\n    <Alert className=\"mx-4 w-auto group-data-[size=sm]/card:mx-3\">\n      <AlertTitle>\n        {auth.localization.settings.reauthenticationTitle}\n      </AlertTitle>\n      <AlertDescription>\n        {auth.localization.settings.reauthenticationDescription}\n      </AlertDescription>\n    </Alert>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/reauthentication.tsx"
    },
    {
      "path": "examples/start-shadcn-example/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 = activeSession.userAgent\n    ? Bowser.parse(activeSession.userAgent)\n    : undefined\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"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/active-sessions.tsx",
      "content": "\"use client\"\n\nimport { isReauthenticationRequiredError } from \"@better-auth-ui/core\"\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 \"cn\"\nimport { ReauthenticationAction } from \"../../reauthentication\"\nimport { ActiveSession } from \"./active-session\"\nimport { SessionActions } from \"./session-actions\"\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 sessionsQuery = useListSessions(authClient, {\n    meta: { errorPresentation: \"inline\" }\n  })\n  const { data: sessions, error, isPending } = sessionsQuery\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(\"gap-0 p-0\", className)}>\n        <CardContent className=\"p-0\">\n          {isReauthenticationRequiredError(error) ? (\n            <ReauthenticationAction />\n          ) : error ? (\n            <div className=\"p-4 text-destructive text-sm\">{error.message}</div>\n          ) : isPending ? (\n            <SessionRowSkeleton />\n          ) : (\n            <ItemGroup className=\"gap-0!\">\n              {activeSessions?.map((activeSession, index) => (\n                <Fragment key={activeSession.id}>\n                  {index > 0 && <ItemSeparator className=\"my-0!\" />}\n                  <ActiveSession activeSession={activeSession} />\n                </Fragment>\n              ))}\n            </ItemGroup>\n          )}\n        </CardContent>\n        {!isPending && !error && (\n          <SessionActions\n            hasOtherSessions={activeSessions.some(\n              (activeSession) => activeSession.id !== session?.session.id\n            )}\n          />\n        )}\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": "examples/start-shadcn-example/src/components/auth/settings/security/session-actions.tsx",
      "content": "\"use client\"\n\nimport {\n  useAuth,\n  useRevokeOtherSessions,\n  useRevokeSessions\n} from \"@better-auth-ui/react\"\nimport { LogOut } from \"lucide-react\"\nimport { useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport {\n  AlertDialog,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogTitle\n} from \"@/components/ui/alert-dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype PendingAction = \"other\" | \"all\"\n\nexport function SessionActions(props: { hasOtherSessions: boolean }) {\n  const { authClient, basePaths, localization, navigate, viewPaths } = useAuth()\n  const [action, setAction] = useState<PendingAction | null>(null)\n\n  const revokeOtherSessions = useRevokeOtherSessions(authClient, {\n    onSuccess: () => {\n      toast.success(localization.settings.signOutOtherDevicesSuccess)\n      setAction(null)\n    }\n  })\n  const revokeSessions = useRevokeSessions(authClient, {\n    onSuccess: () =>\n      navigate({\n        to: `${basePaths.auth}/${viewPaths.auth.signIn}`,\n        replace: true\n      })\n  })\n\n  const isPending = revokeOtherSessions.isPending || revokeSessions.isPending\n  const isEverywhere = action === \"all\"\n\n  return (\n    <>\n      <div className=\"flex flex-wrap justify-end gap-2 border-t p-4\">\n        <Button\n          disabled={!props.hasOtherSessions || isPending}\n          onClick={() => setAction(\"other\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          {localization.settings.signOutOtherDevices}\n        </Button>\n        <Button\n          disabled={isPending}\n          onClick={() => setAction(\"all\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"destructive\"\n        >\n          {localization.settings.signOutEverywhere}\n        </Button>\n      </div>\n\n      <AlertDialog\n        open={action !== null}\n        onOpenChange={(open) => !open && !isPending && setAction(null)}\n      >\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogMedia className=\"bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive\">\n              <LogOut />\n            </AlertDialogMedia>\n            <AlertDialogTitle>\n              {isEverywhere\n                ? localization.settings.signOutEverywhere\n                : localization.settings.signOutOtherDevices}\n            </AlertDialogTitle>\n            <AlertDialogDescription>\n              {isEverywhere\n                ? localization.settings.signOutEverywhereDescription\n                : localization.settings.signOutOtherDevicesDescription}\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel disabled={isPending}>\n              {localization.settings.cancel}\n            </AlertDialogCancel>\n            <Button\n              disabled={isPending}\n              onClick={() =>\n                isEverywhere\n                  ? revokeSessions.mutate()\n                  : revokeOtherSessions.mutate()\n              }\n              type=\"button\"\n              variant={isEverywhere ? \"destructive\" : \"default\"}\n            >\n              {isPending && <Spinner />}\n              {isEverywhere\n                ? localization.settings.signOutEverywhere\n                : localization.settings.signOutOtherDevices}\n            </Button>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/session-actions.tsx"
    }
  ],
  "type": "registry:component"
}