Add "login as user" to user profile card

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 15:55:57 +05:00
parent 3b8a1ce7ee
commit 05f46ec830
3 changed files with 51 additions and 37 deletions
+4
View File
@@ -5,6 +5,7 @@ import { UserEnrollmentManager } from "@/components/admin/user-enrollment-manage
import { UserContactEditor } from "@/components/admin/user-contact-editor"; import { UserContactEditor } from "@/components/admin/user-contact-editor";
import { UserBalanceBlock } from "@/components/admin/user-balance-block"; import { UserBalanceBlock } from "@/components/admin/user-balance-block";
import { ResetPasswordButton } from "@/components/admin/reset-password-button"; import { ResetPasswordButton } from "@/components/admin/reset-password-button";
import { ImpersonateButton } from "@/components/admin/impersonate-button";
interface Props { interface Props {
params: Promise<{ userId: string }>; params: Promise<{ userId: string }>;
@@ -63,6 +64,9 @@ export default async function UserPage({ params }: Props) {
<div> <div>
<h1 className="text-xl font-bold">{user.name}</h1> <h1 className="text-xl font-bold">{user.name}</h1>
<p className="text-sm mt-0.5" style={{ color: "var(--muted-foreground)" }}>{user.email}</p> <p className="text-sm mt-0.5" style={{ color: "var(--muted-foreground)" }}>{user.email}</p>
{user.role !== "admin" && (
<ImpersonateButton userId={userId} className="mt-3" />
)}
</div> </div>
<div className="flex flex-col items-end gap-1"> <div className="flex flex-col items-end gap-1">
<span className="tag-aubade">{roleLabel[user.role] ?? user.role}</span> <span className="tag-aubade">{roleLabel[user.role] ?? user.role}</span>
@@ -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 (
<button
type="button"
onClick={handleImpersonate}
disabled={loading}
className={`text-xs px-2 py-1 transition-colors ${className ?? ""}`}
style={{
border: "1px solid var(--border)",
color: loading ? "var(--muted-foreground)" : "var(--foreground)",
background: "transparent",
}}
>
{loading ? "..." : "Войти как"}
</button>
);
}
+1 -37
View File
@@ -4,6 +4,7 @@ import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { QuickEnrollButton } from "@/components/admin/quick-enroll-modal"; import { QuickEnrollButton } from "@/components/admin/quick-enroll-modal";
import { ImpersonateButton } from "@/components/admin/impersonate-button";
type Enrollment = { type Enrollment = {
courseId: string; 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 (
<button
type="button"
onClick={handleImpersonate}
disabled={loading}
className="text-xs px-2 py-1 transition-colors"
style={{
border: "1px solid var(--border)",
color: loading ? "var(--muted-foreground)" : "var(--foreground)",
background: "transparent",
}}
>
{loading ? "..." : "Войти как"}
</button>
);
}
export function UsersTable({ users }: { users: UserRow[] }) { export function UsersTable({ users }: { users: UserRow[] }) {
const [hoveredId, setHoveredId] = useState<string | null>(null); const [hoveredId, setHoveredId] = useState<string | null>(null);