diff --git a/PostFundManagement.PartnerUI/index.html b/PostFundManagement.PartnerUI/index.html index f266ad6..0f20073 100644 --- a/PostFundManagement.PartnerUI/index.html +++ b/PostFundManagement.PartnerUI/index.html @@ -1,5 +1,5 @@ - + @@ -10,8 +10,8 @@ - -
+ +
diff --git a/PostFundManagement.PartnerUI/src/components/auth/AccountActivationModal.tsx b/PostFundManagement.PartnerUI/src/components/auth/AccountActivationModal.tsx index 7c86149..011d00c 100644 --- a/PostFundManagement.PartnerUI/src/components/auth/AccountActivationModal.tsx +++ b/PostFundManagement.PartnerUI/src/components/auth/AccountActivationModal.tsx @@ -2,18 +2,14 @@ import React, { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { - ShieldAlert, RefreshCw, LogOut, - UserCheck, - CheckCircle2, - AlertTriangle, - ArrowLeft, - Mail, Building2, - Fingerprint, + Clock, + ArrowLeft, + Sparkles, } from 'lucide-react'; -import { BrandLogo } from '../ui/BrandLogo'; +import { PfmPartnerLogo } from '../common/PfmPartnerLogo'; import { AuthUserProfile, UserClaims } from '../../auth/AuthProvider'; import { cancelSessionExpiration, dismissSubordinateModals } from '../../services/sessionExpiration'; @@ -29,7 +25,6 @@ interface AccountActivationModalProps { export const AccountActivationModal: React.FC = ({ user, claims, - sub, onSync, onLogout, initialError, @@ -37,7 +32,6 @@ export const AccountActivationModal: React.FC = ({ const navigate = useNavigate(); const [isSyncing, setIsSyncing] = useState(false); const [errorMessage, setErrorMessage] = useState(initialError || null); - const [syncAttemptCount, setSyncAttemptCount] = useState(0); useEffect(() => { if (initialError !== undefined) { @@ -54,16 +48,9 @@ export const AccountActivationModal: React.FC = ({ user?.name || (claims.name as string) || (claims.preferred_username as string) || - 'Registered Partner'; - - const displayEmail = - user?.email || - (claims.email as string) || - (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) || - ''; + 'Partner'; const tenant = user?.tenant || (claims.tenant as string) || (claims.org as string) || 'Partner Organisation'; - const role = user?.role || (claims.role as string) || 'Partner'; const handleSyncClick = async () => { if (isSyncing) return; @@ -80,10 +67,9 @@ export const AccountActivationModal: React.FC = ({ } if (!result.success) { - setSyncAttemptCount((prev) => prev + 1); setErrorMessage( result.errorMessage || - 'Your partner account is not yet enabled. Please ask your funding administrator to activate your account.' + 'Activation is still being reviewed by your funding administrator. Please check back shortly.' ); } } catch (err: unknown) { @@ -93,10 +79,7 @@ export const AccountActivationModal: React.FC = ({ navigate('/', { replace: true }); return; } - setErrorMessage( - errObj?.message || - 'Account activation check encountered an error. Please confirm with your administrator and try again.' - ); + setErrorMessage('Unable to verify activation right now. Please try again in a few moments.'); } finally { setIsSyncing(false); } @@ -116,147 +99,87 @@ export const AccountActivationModal: React.FC = ({ role="dialog" aria-modal="true" aria-labelledby="activation-modal-title" - className="fixed inset-0 z-[9999] flex items-center justify-center p-4 sm:p-6 bg-[#060a12]/90 backdrop-blur-md animate-in fade-in duration-200 select-none overflow-y-auto" + className="fixed inset-0 z-[9999] flex items-center justify-center p-4 sm:p-6 bg-slate-950/80 backdrop-blur-2xl animate-in fade-in duration-300 select-none overflow-y-auto" > + {/* Background ambient lighting matching LandingPage */} +
+
+
+
e.stopPropagation()} > -
+ {/* Glowing Brand Icon Lockup */} +
+
+ +
-
-
-
-
- - - - - -
-
- - Access Restricted • Activation Pending - -

- {errorMessage?.toLowerCase().includes('tenant') - ? 'Organisation Account Inactive' - : 'Partner Account Pending Activation'} -

-
-
- - + {/* Title & Organization Pill */} +
+
+ + Activation Pending +
+

+ Welcome,{' '} + + {displayName} + +

+
+ + {tenant}
-
-
-
- - Signed In Successfully -
-

- You have signed in successfully, but your partner account is waiting to be activated by the funding administrator. -

-

- Once your administrator confirms your account is active, click{' '} - Check Account Status below to open your dashboard. -

-
- -
-
- Your Partner Profile - - Pending Activation - -
- -
-
- -
- Name - {displayName} -
-
- -
- -
- Email - {displayEmail || 'Not specified'} -
-
- -
- -
- Organisation / Role - - {tenant} ({role}) - -
-
- -
- -
- Account ID - {sub || 'SSO-Principal'} -
-
-
-
- - {errorMessage && ( -
- -
-

Account Not Activated Yet

-

{errorMessage}

- {syncAttemptCount > 0 && ( -

- Checked #{syncAttemptCount} at {new Date().toLocaleTimeString()} -

- )} -
-
- )} -
- -
-
- - - + {/* Core Friendly Message */} +

+ Your credentials have been authenticated. Your funding administrator is finalizing your portfolio access. +

+ + {/* Notice badge if check returned not yet ready */} + {errorMessage && ( +
+ + {errorMessage}
+ )} + {/* Action Button matching Specular / Landing styling */} +
+ + {/* Minimal Secondary Actions */} +
+ + • + +
@@ -266,4 +189,3 @@ export const AccountActivationModal: React.FC = ({ ? createPortal(modalContent, document.body) : modalContent; }; - diff --git a/PostFundManagement.PartnerUI/src/components/auth/NonPartnerAccessDeniedModal.tsx b/PostFundManagement.PartnerUI/src/components/auth/NonPartnerAccessDeniedModal.tsx index cf67728..5c44393 100644 --- a/PostFundManagement.PartnerUI/src/components/auth/NonPartnerAccessDeniedModal.tsx +++ b/PostFundManagement.PartnerUI/src/components/auth/NonPartnerAccessDeniedModal.tsx @@ -1,16 +1,8 @@ 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 { ShieldCheck, LogOut, ArrowLeft, Lock } from 'lucide-react'; +import { PfmPartnerLogo } from '../common/PfmPartnerLogo'; import { AuthUserProfile, UserClaims } from '../../auth/AuthProvider'; import { cancelSessionExpiration, dismissSubordinateModals } from '../../services/sessionExpiration'; @@ -22,14 +14,9 @@ interface NonPartnerAccessDeniedModalProps { onLogout: () => Promise; } -/** - * 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 = ({ user, claims, - sub, roles, onLogout, }) => { @@ -40,133 +27,79 @@ export const NonPartnerAccessDeniedModal: React.FC 0 ? roles.join(', ') : user?.role || (claims.role as string) || 'Unassigned'; + const assignedRolesDisplay = roles.length > 0 ? roles.join(', ') : user?.role || (claims.role as string) || 'Standard User'; const handleLogoutClick = async () => { await onLogout(); navigate('/', { replace: true }); }; + const handleLandingClick = () => { + navigate('/'); + }; + const modalContent = (
-
-
+
+
+
-
-
-
- -
-
-
- - - Partner Role Required - - Security Policy -
-

- Access Denied: Partner Accounts Only -

-

- This application is exclusively reserved for users assigned the Partner role. Your account role ({assignedRolesDisplay}) is not permitted on the Partner Portal. -

-
-
- -
-
- Account Name - {displayName} -
- - {displayEmail && ( -
- - - Email - - {displayEmail} -
- )} - -
- - - Assigned Role(s) - - - {assignedRolesDisplay} - -
- - {tenant && ( -
- - - Organisation / Tenant - - {tenant} -
- )} - - {sub && ( -
- - - Subject Key - - {sub} -
- )} -
- -
-

- - Portal Separation Notice -

-

- Internal staff, executives, programme managers, and administrators must sign in through the PFM Management Console. Only external grant recipients and project partners with the Partner role are permitted to use this portal. -

-
- -
- -
+
e.stopPropagation()} + > +
+
+
-
-
- - PFM Partner Portal +
+
+ + Partner Access Only
- Role Security Gate +

+ Role Authorization Required +

+
+ +

+ This portal is reserved for registered Grantee and Partner accounts. Your current profile ({assignedRolesDisplay}) does not have access. +

+ +
+ Signed in as {displayEmail} +
+ +
+ + +
@@ -176,4 +109,3 @@ export const NonPartnerAccessDeniedModal: React.FC = ({ const modalContent = (
setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} role="dialog" aria-modal="true" aria-labelledby="session-expired-title" > +
+
+
+
-
- -
-
-
- -
-
-
-

- Session Expired -

- - HTTP 401 - -
-

Please sign in again to continue

-
-
- - -
- -
-

- Your active working session has ended for security. To protect your information, please return to the home page and sign in again. -

- {reason && ( -
- - {reason} -
- )} +
+
+
-
- - - Returning to home page in{' '} - {secondsRemaining}s - - {isPaused && ( - - Paused on hover - - )} +
+ + Session Expired
+

+ Security Session Ended +

+
-
+

+ {reason || 'Your security credentials have expired for your protection. Please sign in again to continue.'} +

+ + {/* Smooth Countdown Bar */} +
+
+ + Redirecting in {secondsRemaining}s +
-
+
@@ -179,4 +149,3 @@ export const SessionExpiredModal: React.FC = ({ ? createPortal(modalContent, document.body) : modalContent; }; - diff --git a/PostFundManagement.PartnerUI/src/index.css b/PostFundManagement.PartnerUI/src/index.css index 1d89e5d..87916d6 100644 --- a/PostFundManagement.PartnerUI/src/index.css +++ b/PostFundManagement.PartnerUI/src/index.css @@ -1,7 +1,13 @@ @import "tailwindcss"; +:root { + color-scheme: dark; +} + @layer base { - body { + html, body { + background-color: #020617; /* slate-950 */ + color: #f8fafc; /* slate-100 */ font-family: 'Plus Jakarta Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }