fix(impersonation): pointer-events + show all roles (§D.4-5)
Banner wrapper now uses pointer-events-none so it doesn't intercept clicks on the user-menu dropdown sitting underneath; the 'Return to Admin' button re-enables pointer events on itself only. Banner also lists every role the impersonated user holds (e.g. 'JURY MEMBER, MENTOR') instead of just the primary role, matching how multi-role users are surfaced everywhere else. Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
This commit is contained in:
@@ -1,15 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { ArrowLeft, Loader2 } from 'lucide-react'
|
import { ArrowLeft, Loader2 } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import type { UserRole } from '@prisma/client'
|
||||||
|
|
||||||
export function ImpersonationBanner() {
|
export function ImpersonationBanner() {
|
||||||
const { data: session, update } = useSession()
|
const { data: session, update } = useSession()
|
||||||
const router = useRouter()
|
|
||||||
const endImpersonation = trpc.user.endImpersonation.useMutation()
|
const endImpersonation = trpc.user.endImpersonation.useMutation()
|
||||||
|
|
||||||
if (!session?.user?.impersonating) return null
|
if (!session?.user?.impersonating) return null
|
||||||
@@ -18,23 +17,29 @@ export function ImpersonationBanner() {
|
|||||||
try {
|
try {
|
||||||
await endImpersonation.mutateAsync()
|
await endImpersonation.mutateAsync()
|
||||||
await update({ endImpersonation: true })
|
await update({ endImpersonation: true })
|
||||||
// Full page navigation to ensure updated JWT cookie is sent
|
|
||||||
window.location.href = '/admin/members'
|
window.location.href = '/admin/members'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : 'Failed to end impersonation')
|
toast.error(error instanceof Error ? error.message : 'Failed to end impersonation')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show every role the impersonated user holds (multi-role support).
|
||||||
|
const roles = (session.user.roles as UserRole[] | undefined) ?? [session.user.role as UserRole]
|
||||||
|
const rolesLabel = roles.map((r) => r.replace(/_/g, ' ')).join(', ')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 right-0 z-50 flex items-center justify-center gap-3 bg-red-600 px-4 py-1.5 text-sm text-white shadow-md">
|
// pointer-events-none on the wrapper so the banner doesn't intercept clicks
|
||||||
|
// on anything underneath (e.g., the user-menu dropdown). The button inside
|
||||||
|
// re-enables pointer-events on itself only.
|
||||||
|
<div className="pointer-events-none fixed top-0 left-0 right-0 z-50 flex items-center justify-center gap-3 bg-red-600 px-4 py-1.5 text-sm text-white shadow-md">
|
||||||
<span>
|
<span>
|
||||||
Impersonating <strong>{session.user.name || session.user.email}</strong>{' '}
|
Impersonating <strong>{session.user.name || session.user.email}</strong>{' '}
|
||||||
({session.user.role.replace('_', ' ')})
|
({rolesLabel})
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="h-6 px-3 text-xs"
|
className="pointer-events-auto h-6 px-3 text-xs"
|
||||||
onClick={handleReturn}
|
onClick={handleReturn}
|
||||||
disabled={endImpersonation.isPending}
|
disabled={endImpersonation.isPending}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user