{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-session",
  "title": "Multi Session",
  "description": "Multi-session plugin: Enable multiple concurrent sessions with account switching. Registers account management cards in account settings and a switch account submenu in the user button.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "better-auth",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "dropdown-menu",
    "item",
    "separator",
    "sonner",
    "spinner",
    "https://better-auth-ui.com/r/radix-nova/user-view.json"
  ],
  "files": [
    {
      "path": "src/lib/auth/auth-plugin.ts",
      "content": "import type {\n  AuthPluginComponents,\n  AuthPlugin as AuthPluginPrimitive\n} from \"@better-auth-ui/react\"\n\ndeclare module \"@better-auth-ui/core\" {\n  /** Widens `useAuth().plugins` to the shadcn-typed `AuthPlugin`. */\n  interface AuthPluginRegister {\n    shadcn: AuthPlugin\n  }\n}\n\n/** Props the shadcn `<Auth>` router spreads onto plugin-contributed auth views. */\nexport type AuthViewProps = {\n  className?: string\n  socialLayout?: \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n  socialPosition?: \"top\" | \"bottom\"\n}\n\n/** Props the shadcn `<Settings>` router spreads onto plugin-contributed settings views. */\nexport type SettingsViewProps = {\n  className?: string\n}\n\n/** Shadcn plugin type. Plugin authors import this from `@/lib/auth/auth-plugin`. */\nexport type AuthPlugin = AuthPluginPrimitive<\n  AuthPluginComponents,\n  AuthViewProps,\n  SettingsViewProps\n>\n",
      "type": "registry:lib",
      "target": "@lib/auth/auth-plugin.ts"
    },
    {
      "path": "src/lib/auth/multi-session-plugin.ts",
      "content": "import { createAuthPlugin } from \"@better-auth-ui/core\"\nimport {\n  multiSessionPlugin as coreMultiSessionPlugin,\n  type MultiSessionPluginOptions\n} from \"@better-auth-ui/core/plugins\"\n\nimport { ManageAccounts } from \"@/components/auth/multi-session/manage-accounts\"\nimport { SwitchAccountSubmenu } from \"@/components/auth/multi-session/switch-account-submenu\"\n\nexport const multiSessionPlugin = createAuthPlugin(\n  coreMultiSessionPlugin.id,\n  (options: MultiSessionPluginOptions = {}) => ({\n    ...coreMultiSessionPlugin(options),\n    accountCards: [ManageAccounts],\n    userMenuItems: [SwitchAccountSubmenu]\n  })\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/multi-session-plugin.ts"
    },
    {
      "path": "src/components/auth/multi-session/manage-accounts.tsx",
      "content": "\"use client\"\n\nimport {\n  type MultiSessionAuthClient,\n  useAuth,\n  useAuthPlugin,\n  useListDeviceSessions,\n  useSession\n} from \"@better-auth-ui/react\"\nimport { Fragment } from \"react\"\n\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { ItemGroup, ItemSeparator } from \"@/components/ui/item\"\nimport { multiSessionPlugin } from \"@/lib/auth/multi-session-plugin\"\nimport { cn } from \"@/lib/utils\"\nimport { ManageAccount } from \"./manage-account\"\n\nexport type ManageAccountsProps = {\n  className?: string\n}\n\n/**\n * Render a card that lists and manages all device sessions for the current user.\n *\n * Shows each session with user information and actions to switch to or revoke a session.\n * When device session data is loading, a pending placeholder row is displayed.\n *\n * @returns A JSX element containing the accounts management card\n */\nexport function ManageAccounts({ className }: ManageAccountsProps) {\n  const { authClient } = useAuth()\n  const { localization: multiSessionLocalization } =\n    useAuthPlugin(multiSessionPlugin)\n  const { data: session } = useSession(authClient)\n\n  const { data: deviceSessions, isPending } = useListDeviceSessions(\n    authClient as MultiSessionAuthClient\n  )\n\n  const otherSessions = deviceSessions?.filter(\n    (deviceSession) => deviceSession.session.id !== session?.session.id\n  )\n\n  const allRows = [\n    {\n      key: session?.session.id ?? \"current\",\n      deviceSession: !isPending ? session : null,\n      isPending\n    },\n    ...(otherSessions?.map((deviceSession) => ({\n      key: deviceSession.session.id,\n      deviceSession,\n      isPending: false\n    })) ?? [])\n  ]\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {multiSessionLocalization.manageAccounts}\n      </h2>\n\n      <Card className={cn(\"p-0\", className)}>\n        <CardContent className=\"p-0\">\n          <ItemGroup className=\"gap-0\">\n            {allRows.map((row, index) => (\n              <Fragment key={row.key}>\n                {index > 0 && <ItemSeparator />}\n                <ManageAccount\n                  deviceSession={row.deviceSession}\n                  isPending={row.isPending}\n                />\n              </Fragment>\n            ))}\n          </ItemGroup>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/multi-session/manage-accounts.tsx"
    },
    {
      "path": "src/components/auth/multi-session/manage-account.tsx",
      "content": "\"use client\"\n\nimport {\n  type ListDeviceSession,\n  type MultiSessionAuthClient,\n  useAuth,\n  useAuthPlugin,\n  useRevokeMultiSession,\n  useSession,\n  useSetActiveSession\n} from \"@better-auth-ui/react\"\nimport { ArrowLeftRight, LogOut, MoreHorizontal } from \"lucide-react\"\nimport { toast } from \"sonner\"\nimport { UserView } from \"@/components/auth/user/user-view\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { Item, ItemActions } from \"@/components/ui/item\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { multiSessionPlugin } from \"@/lib/auth/multi-session-plugin\"\nimport { cn } from \"@/lib/utils\"\n\nexport type ManageAccountProps = {\n  deviceSession?: ListDeviceSession | null\n  isPending?: boolean\n}\n\n/**\n * Render a single account row with user info and switch/revoke controls.\n *\n * Shows the user's avatar and info. For the active session, shows a sign-out button.\n * For non-active sessions, shows a dropdown menu with switch and sign-out options.\n *\n * @param deviceSession - The device session object containing session and user data\n * @param isPending - Whether the device session is pending\n * @returns A JSX element containing the account row\n */\nexport function ManageAccount({\n  deviceSession,\n  isPending\n}: ManageAccountProps) {\n  const { authClient, localization } = useAuth()\n  const { localization: multiSessionLocalization } =\n    useAuthPlugin(multiSessionPlugin)\n  const { data: session } = useSession(authClient)\n\n  const { mutate: setActiveSession, isPending: isSwitching } =\n    useSetActiveSession(authClient as MultiSessionAuthClient, {\n      onSuccess: () => window.scrollTo({ top: 0 })\n    })\n\n  const { mutate: revokeSession, isPending: isRevoking } =\n    useRevokeMultiSession(authClient as MultiSessionAuthClient, {\n      onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)\n    })\n\n  const isActive = deviceSession?.session.userId === session?.session.userId\n  const isBusy = isSwitching || isRevoking\n\n  return (\n    <Item>\n      <UserView user={deviceSession?.user} isPending={isPending} />\n      <ItemActions>\n        {deviceSession && isActive && (\n          <Button\n            className=\"shrink-0\"\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={() =>\n              revokeSession({ sessionToken: deviceSession.session.token })\n            }\n            disabled={isBusy}\n          >\n            {isRevoking ? <Spinner /> : <LogOut />}\n            {localization.auth.signOut}\n          </Button>\n        )}\n\n        {deviceSession && !isActive && (\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              className={cn(\n                buttonVariants({ variant: \"ghost\", size: \"icon-sm\" }),\n                \"shrink-0\"\n              )}\n              disabled={isBusy}\n            >\n              <MoreHorizontal />\n            </DropdownMenuTrigger>\n\n            <DropdownMenuContent align=\"end\" className=\"min-w-fit\">\n              <DropdownMenuItem\n                onClick={() =>\n                  setActiveSession({\n                    sessionToken: deviceSession.session.token\n                  })\n                }\n              >\n                <ArrowLeftRight className=\"text-muted-foreground\" />\n                {multiSessionLocalization.switchAccount}\n              </DropdownMenuItem>\n\n              <DropdownMenuItem\n                onClick={() =>\n                  revokeSession({\n                    sessionToken: deviceSession.session.token\n                  })\n                }\n              >\n                <LogOut className=\"text-muted-foreground\" />\n                {localization.auth.signOut}\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        )}\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/multi-session/manage-account.tsx"
    },
    {
      "path": "src/components/auth/multi-session/switch-account-submenu.tsx",
      "content": "\"use client\"\n\nimport { useAuth, useAuthPlugin, useSession } from \"@better-auth-ui/react\"\nimport { ArrowLeftRight } from \"lucide-react\"\n\nimport {\n  DropdownMenuSub,\n  DropdownMenuSubTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { multiSessionPlugin } from \"@/lib/auth/multi-session-plugin\"\nimport { SwitchAccountSubmenuContent } from \"./switch-account-submenu-content\"\n\nexport type SwitchAccountSubmenuProps = {\n  className?: string\n}\n\n/**\n * Render a submenu trigger for switching between multiple authenticated sessions.\n *\n * This component renders as a dropdown menu item that opens a submenu containing\n * the switch account menu. It should be rendered inside the UserButton dropdown\n * as a userMenuItem from the multiSessionPlugin.\n *\n * @param className - Optional additional CSS class names\n * @returns The switch account submenu as a JSX element\n */\nexport function SwitchAccountSubmenu({ className }: SwitchAccountSubmenuProps) {\n  const { authClient } = useAuth()\n  const { data: session } = useSession(authClient)\n  const { localization: multiSessionLocalization } =\n    useAuthPlugin(multiSessionPlugin)\n\n  if (!session) {\n    return null\n  }\n\n  return (\n    <DropdownMenuSub>\n      <DropdownMenuSubTrigger className={className}>\n        <ArrowLeftRight className=\"text-muted-foreground\" />\n\n        {multiSessionLocalization.switchAccount}\n      </DropdownMenuSubTrigger>\n\n      <SwitchAccountSubmenuContent />\n    </DropdownMenuSub>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/multi-session/switch-account-submenu.tsx"
    },
    {
      "path": "src/components/auth/multi-session/switch-account-submenu-content.tsx",
      "content": "\"use client\"\n\nimport {\n  type MultiSessionAuthClient,\n  useAuth,\n  useAuthPlugin,\n  useListDeviceSessions,\n  useSession\n} from \"@better-auth-ui/react\"\nimport { Check, CirclePlus } from \"lucide-react\"\nimport { UserView } from \"@/components/auth/user/user-view\"\nimport {\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuSubContent\n} from \"@/components/ui/dropdown-menu\"\nimport { multiSessionPlugin } from \"@/lib/auth/multi-session-plugin\"\nimport { SwitchAccountSubmenuItem } from \"./switch-account-submenu-item\"\n\n/**\n * Render the submenu content for switching between multiple authenticated sessions.\n *\n * Shows the current session with a checkmark, lists other device sessions that can be activated,\n * and provides an option to add a new account. This component should be rendered inside a\n * DropdownMenuSub to defer the useListDeviceSessions query until the submenu is opened.\n *\n * @returns The switch account submenu content as a JSX element\n */\nexport function SwitchAccountSubmenuContent() {\n  const { authClient, basePaths, viewPaths, navigate } = useAuth()\n  const { localization: multiSessionLocalization } =\n    useAuthPlugin(multiSessionPlugin)\n  const { data: session } = useSession(authClient)\n  const { data: deviceSessions, isPending } = useListDeviceSessions(\n    authClient as MultiSessionAuthClient\n  )\n\n  return (\n    <DropdownMenuSubContent className=\"min-w-48 md:min-w-56 max-w-[48svw]\">\n      <DropdownMenuItem>\n        <UserView isPending={isPending} />\n\n        {!isPending && <Check className=\"ml-auto\" />}\n      </DropdownMenuItem>\n\n      {deviceSessions\n        ?.filter(\n          (deviceSession) => deviceSession.session.id !== session?.session.id\n        )\n        .map((deviceSession) => (\n          <SwitchAccountSubmenuItem\n            key={deviceSession.session.id}\n            deviceSession={deviceSession}\n          />\n        ))}\n\n      <DropdownMenuSeparator />\n\n      <DropdownMenuItem\n        onClick={() =>\n          navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })\n        }\n      >\n        <CirclePlus className=\"text-muted-foreground\" />\n\n        {multiSessionLocalization.addAccount}\n      </DropdownMenuItem>\n    </DropdownMenuSubContent>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/multi-session/switch-account-submenu-content.tsx"
    },
    {
      "path": "src/components/auth/multi-session/switch-account-submenu-item.tsx",
      "content": "\"use client\"\n\nimport {\n  type ListDeviceSession,\n  type MultiSessionAuthClient,\n  useAuth,\n  useSetActiveSession\n} from \"@better-auth-ui/react\"\nimport { UserView } from \"@/components/auth/user/user-view\"\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\nexport type SwitchAccountSubmenuItemProps = {\n  deviceSession: ListDeviceSession\n}\n\n/**\n * Render a dropdown menu item for switching to a different authenticated session.\n *\n * @param deviceSession - The device session to display and switch to when selected\n * @returns The switch account dropdown menu item as a JSX element\n */\nexport function SwitchAccountSubmenuItem({\n  deviceSession\n}: SwitchAccountSubmenuItemProps) {\n  const { authClient } = useAuth()\n  const { mutate: setActiveSession, isPending } = useSetActiveSession(\n    authClient as MultiSessionAuthClient,\n    {\n      onSuccess: () => window.scrollTo({ top: 0 })\n    }\n  )\n\n  return (\n    <DropdownMenuItem\n      disabled={isPending}\n      onClick={() =>\n        setActiveSession({ sessionToken: deviceSession.session.token })\n      }\n    >\n      <UserView user={deviceSession.user} />\n\n      {isPending && <Spinner className=\"ml-auto size-4\" />}\n    </DropdownMenuItem>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/multi-session/switch-account-submenu-item.tsx"
    }
  ],
  "type": "registry:component"
}