From 05f46ec830b8f1cc3b4b0df0dd2308acf03d4556 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Tue, 9 Jun 2026 15:55:57 +0500 Subject: [PATCH] Add "login as user" to user profile card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract ImpersonateButton into a shared component and reuse it on the admin user profile page (header, hidden for admins) — same behaviour as the users list. Co-Authored-By: Claude Opus 4.8 --- src/app/admin/users/[userId]/page.tsx | 4 ++ src/components/admin/impersonate-button.tsx | 46 +++++++++++++++++++++ src/components/admin/users-table.tsx | 38 +---------------- 3 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 src/components/admin/impersonate-button.tsx diff --git a/src/app/admin/users/[userId]/page.tsx b/src/app/admin/users/[userId]/page.tsx index b89117e..f028f71 100644 --- a/src/app/admin/users/[userId]/page.tsx +++ b/src/app/admin/users/[userId]/page.tsx @@ -5,6 +5,7 @@ import { UserEnrollmentManager } from "@/components/admin/user-enrollment-manage import { UserContactEditor } from "@/components/admin/user-contact-editor"; import { UserBalanceBlock } from "@/components/admin/user-balance-block"; import { ResetPasswordButton } from "@/components/admin/reset-password-button"; +import { ImpersonateButton } from "@/components/admin/impersonate-button"; interface Props { params: Promise<{ userId: string }>; @@ -63,6 +64,9 @@ export default async function UserPage({ params }: Props) {

{user.name}

{user.email}

+ {user.role !== "admin" && ( + + )}
{roleLabel[user.role] ?? user.role} diff --git a/src/components/admin/impersonate-button.tsx b/src/components/admin/impersonate-button.tsx new file mode 100644 index 0000000..3cdcb7b --- /dev/null +++ b/src/components/admin/impersonate-button.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useState } from "react"; + +export function ImpersonateButton({ + userId, + className, +}: { + userId: string; + className?: string; +}) { + const [loading, setLoading] = useState(false); + + async function handleImpersonate() { + setLoading(true); + try { + const res = await fetch("/api/auth/admin/impersonate-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId }), + credentials: "include", + }); + if (!res.ok) throw new Error(await res.text()); + window.location.href = "/dashboard"; + } catch (e) { + console.error("Impersonation failed:", e); + setLoading(false); + } + } + + return ( + + ); +} diff --git a/src/components/admin/users-table.tsx b/src/components/admin/users-table.tsx index 75917be..e6586f2 100644 --- a/src/components/admin/users-table.tsx +++ b/src/components/admin/users-table.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import { QuickEnrollButton } from "@/components/admin/quick-enroll-modal"; +import { ImpersonateButton } from "@/components/admin/impersonate-button"; type Enrollment = { courseId: string; @@ -91,43 +92,6 @@ function UserPopup({ user }: { user: UserRow }) { ); } -function ImpersonateButton({ userId }: { userId: string }) { - const [loading, setLoading] = useState(false); - - async function handleImpersonate() { - setLoading(true); - try { - const res = await fetch("/api/auth/admin/impersonate-user", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ userId }), - credentials: "include", - }); - if (!res.ok) throw new Error(await res.text()); - window.location.href = "/dashboard"; - } catch (e) { - console.error("Impersonation failed:", e); - setLoading(false); - } - } - - return ( - - ); -} - export function UsersTable({ users }: { users: UserRow[] }) { const [hoveredId, setHoveredId] = useState(null);