diff --git a/frontend/src/layouts/PlatformLayout.tsx b/frontend/src/layouts/PlatformLayout.tsx index 924f2159..744ecec1 100644 --- a/frontend/src/layouts/PlatformLayout.tsx +++ b/frontend/src/layouts/PlatformLayout.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import { Moon, Sun, Globe, Menu } from 'lucide-react'; import { User } from '../types'; @@ -8,8 +8,11 @@ import NotificationDropdown from '../components/NotificationDropdown'; import LanguageSelector from '../components/LanguageSelector'; import TicketModal from '../components/TicketModal'; import HelpButton from '../components/HelpButton'; +import MasqueradeBanner from '../components/MasqueradeBanner'; import { useTicket } from '../hooks/useTickets'; import { useScrollToTop } from '../hooks/useScrollToTop'; +import { useStopMasquerade } from '../hooks/useAuth'; +import { MasqueradeStackEntry } from '../api/auth'; interface PlatformLayoutProps { user: User; @@ -30,6 +33,34 @@ const PlatformLayout: React.FC = ({ user, darkMode, toggleT useScrollToTop(mainContentRef); + // Masquerade logic + const [masqueradeStack, setMasqueradeStack] = useState([]); + const stopMasqueradeMutation = useStopMasquerade(); + + useEffect(() => { + const stackJson = localStorage.getItem('masquerade_stack'); + if (stackJson) { + try { + setMasqueradeStack(JSON.parse(stackJson)); + } catch (e) { + console.error('Failed to parse masquerade stack data', e); + } + } + }, [user.id]); + + const handleStopMasquerade = () => { + stopMasqueradeMutation.mutate(); + }; + + const originalUser = masqueradeStack.length > 0 + ? { + id: masqueradeStack[0].user_id, + username: masqueradeStack[0].username, + name: masqueradeStack[0].username, + role: masqueradeStack[0].role, + } as User + : null; + // Fetch ticket data when modal is opened from notification const { data: ticketFromNotification } = useTicket(ticketModalId && ticketModalId !== 'undefined' ? ticketModalId : undefined); @@ -56,6 +87,16 @@ const PlatformLayout: React.FC = ({ user, darkMode, toggleT {/* Main Content Area */}
+ {/* Masquerade Banner */} + {originalUser && ( + + )} + {/* Platform Top Bar */}
diff --git a/frontend/src/layouts/__tests__/PlatformLayout.test.tsx b/frontend/src/layouts/__tests__/PlatformLayout.test.tsx index 675d2c7f..43acfb14 100644 --- a/frontend/src/layouts/__tests__/PlatformLayout.test.tsx +++ b/frontend/src/layouts/__tests__/PlatformLayout.test.tsx @@ -92,6 +92,23 @@ vi.mock('../../hooks/useScrollToTop', () => ({ useScrollToTop: vi.fn(), })); +vi.mock('../../hooks/useAuth', () => ({ + useStopMasquerade: () => ({ + mutate: vi.fn(), + isPending: false, + }), +})); + +vi.mock('../../components/MasqueradeBanner', () => ({ + default: ({ effectiveUser, originalUser, onStop }: any) => ( +
+ {effectiveUser.name} + {originalUser.name} + +
+ ), +})); + // Mock react-router-dom Outlet vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom');