Files
pfm/PostFundManagement.PartnerUI/src/components/auth/NonPartnerAccessDeniedModal.tsx
T
2026-10-23 00:58:47 +02:00

180 lines
7.5 KiB
TypeScript

import React, { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import {
ShieldAlert,
LogOut,
Ban,
Mail,
Building2,
Fingerprint,
Lock,
} from 'lucide-react';
import { BrandLogo } from '../ui/BrandLogo';
import { AuthUserProfile, UserClaims } from '../../auth/AuthProvider';
import { cancelSessionExpiration, dismissSubordinateModals } from '../../services/sessionExpiration';
interface NonPartnerAccessDeniedModalProps {
user: AuthUserProfile | null;
claims: UserClaims;
sub: string;
roles: string[];
onLogout: () => Promise<void>;
}
/**
* Blocks any authenticated user who does NOT hold the 'Partner' role.
* RBAC on PartnerUI strictly permits only Partner roles; no other roles are allowed.
*/
export const NonPartnerAccessDeniedModal: React.FC<NonPartnerAccessDeniedModalProps> = ({
user,
claims,
sub,
roles,
onLogout,
}) => {
const navigate = useNavigate();
useEffect(() => {
cancelSessionExpiration();
dismissSubordinateModals();
}, []);
const displayName =
user?.name ||
(claims.name as string) ||
(claims.preferred_username as string) ||
'Authenticated User';
const displayEmail =
user?.email ||
(claims.email as string) ||
(claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) ||
'';
const tenant = user?.tenant || (claims.tenant as string) || (claims.org as string) || 'Statutory Enterprise';
const assignedRolesDisplay = roles.length > 0 ? roles.join(', ') : user?.role || (claims.role as string) || 'Unassigned';
const handleLogoutClick = async () => {
await onLogout();
navigate('/', { replace: true });
};
const modalContent = (
<div
role="alertdialog"
aria-modal="true"
aria-labelledby="non-partner-denied-title"
aria-describedby="non-partner-denied-description"
className="fixed inset-0 z-[9999] flex items-center justify-center p-4 bg-black/85 backdrop-blur-md animate-in fade-in duration-200 overflow-y-auto"
>
<div className="relative w-full max-w-lg overflow-hidden rounded-2xl bg-[#0e1424] border border-rose-900/60 shadow-2xl shadow-rose-950/50 my-auto">
<div className="h-2 w-full bg-gradient-to-r from-rose-600 via-red-500 to-amber-500" />
<div className="p-6 sm:p-8 space-y-6">
<div className="flex items-start gap-4">
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-rose-500/15 border border-rose-500/30 text-rose-400">
<Ban className="h-6 w-6 text-rose-400" />
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-1 text-[11px] font-semibold text-rose-400 bg-rose-950/60 px-2 py-0.5 rounded-md border border-rose-800/60">
<Lock className="w-3 h-3" />
Partner Role Required
</span>
<span className="text-xs text-slate-500">Security Policy</span>
</div>
<h2 id="non-partner-denied-title" className="text-lg font-bold text-white tracking-tight">
Access Denied: Partner Accounts Only
</h2>
<p id="non-partner-denied-description" className="text-xs text-slate-400 leading-relaxed">
This application is exclusively reserved for users assigned the <strong className="text-emerald-300">Partner</strong> role. Your account role (<strong className="text-rose-300">{assignedRolesDisplay}</strong>) is not permitted on the Partner Portal.
</p>
</div>
</div>
<div className="rounded-xl bg-slate-900/80 border border-slate-800 p-4 space-y-3 text-xs">
<div className="flex items-center justify-between pb-2 border-b border-slate-800/80">
<span className="text-slate-400 font-medium">Account Name</span>
<span className="text-white font-semibold">{displayName}</span>
</div>
{displayEmail && (
<div className="flex items-center justify-between pb-2 border-b border-slate-800/80">
<span className="text-slate-400 font-medium flex items-center gap-1.5">
<Mail className="w-3.5 h-3.5 text-slate-500" />
Email
</span>
<span className="text-slate-200 font-mono">{displayEmail}</span>
</div>
)}
<div className="flex items-center justify-between pb-2 border-b border-slate-800/80">
<span className="text-slate-400 font-medium flex items-center gap-1.5">
<Building2 className="w-3.5 h-3.5 text-slate-500" />
Assigned Role(s)
</span>
<span className="text-rose-400 font-semibold bg-rose-950/60 px-2 py-0.5 rounded border border-rose-800/50">
{assignedRolesDisplay}
</span>
</div>
{tenant && (
<div className="flex items-center justify-between pb-2 border-b border-slate-800/80">
<span className="text-slate-400 font-medium flex items-center gap-1.5">
<Building2 className="w-3.5 h-3.5 text-slate-500" />
Organisation / Tenant
</span>
<span className="text-slate-200 font-medium truncate max-w-[220px]">{tenant}</span>
</div>
)}
{sub && (
<div className="flex items-center justify-between text-[11px] text-slate-500">
<span className="flex items-center gap-1">
<Fingerprint className="w-3 h-3" />
Subject Key
</span>
<span className="font-mono text-slate-400 truncate max-w-[200px]">{sub}</span>
</div>
)}
</div>
<div className="p-4 rounded-xl bg-rose-950/20 border border-rose-900/40 text-xs text-rose-200 leading-relaxed space-y-1.5">
<p className="font-semibold text-rose-300 flex items-center gap-1.5">
<ShieldAlert className="w-4 h-4 text-rose-400" />
Portal Separation Notice
</p>
<p className="text-[11px] text-rose-300/90">
Internal staff, executives, programme managers, and administrators must sign in through the PFM Management Console. Only external grant recipients and project partners with the <strong>Partner</strong> role are permitted to use this portal.
</p>
</div>
<div className="pt-2">
<button
onClick={handleLogoutClick}
className="w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 hover:text-white text-xs font-semibold border border-slate-700 transition cursor-pointer shadow-sm"
>
<LogOut className="w-4 h-4 text-rose-400" />
<span>Sign Out & Return to Home</span>
</button>
</div>
</div>
<div className="px-6 py-3 bg-[#0a0f1d] border-t border-slate-800/80 flex items-center justify-between text-[11px] text-slate-500">
<div className="flex items-center gap-2">
<BrandLogo size={16} showGlow={false} animateOnHover={false} />
<span>PFM Partner Portal</span>
</div>
<span>Role Security Gate</span>
</div>
</div>
</div>
);
return typeof document !== 'undefined'
? createPortal(modalContent, document.body)
: modalContent;
};