Implemented session and account lockout management

Enabled account sync control and session expiration management
Applied some fixes to the govername page and project page
This commit is contained in:
2026-09-18 10:19:24 +02:00
parent 41bf9b63bf
commit ec777fabf8
24 changed files with 3888 additions and 500 deletions
@@ -15,6 +15,7 @@ import {
Search,
Coins,
Eye,
RefreshCw,
} from 'lucide-react';
import {
getOrganisations,
@@ -49,6 +50,7 @@ import {
import {
getOrganisationTypeLabel,
getActivityStatusBadge,
getOrganisationStatusBadge,
getApprovalStatusBadge,
formatDate,
getUserEmail,
@@ -192,7 +194,7 @@ export const GovernancePage: React.FC = () => {
orgForm.reset();
},
onError: (err: unknown) => {
toastError('Failed to save organisation', String(err));
toastError('Failed to save organisation', err);
},
});
@@ -217,7 +219,7 @@ export const GovernancePage: React.FC = () => {
portfolioForm.reset();
},
onError: (err: unknown) => {
toastError('Failed to save portfolio', String(err));
toastError('Failed to save portfolio', err);
},
});
@@ -242,7 +244,7 @@ export const GovernancePage: React.FC = () => {
programmeForm.reset();
},
onError: (err: unknown) => {
toastError('Failed to save programme', String(err));
toastError('Failed to save programme', err);
},
});
@@ -409,17 +411,31 @@ export const GovernancePage: React.FC = () => {
</button>
</div>
{/* Search Bar */}
{/* Search Bar & Actions */}
{activeTab === 'organisations' && (
<div className="relative max-w-md">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
<input
type="text"
placeholder="Search by organisation name, reg no, or email..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
/>
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
<div className="relative max-w-md flex-1">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
<input
type="text"
placeholder="Search by organisation name, reg no, or email..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
/>
</div>
<button
type="button"
onClick={() => {
queryClient.invalidateQueries({ queryKey: ['organisations'] });
success('Refreshed', 'Organisation directory refreshed from database.');
}}
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer"
title="Force refresh organisation directory from database"
>
<RefreshCw className="w-3.5 h-3.5 text-blue-400" />
Refresh Directory
</button>
</div>
)}
@@ -453,7 +469,7 @@ export const GovernancePage: React.FC = () => {
</tr>
) : (
filteredOrgs.map((org) => {
const statusBadge = getActivityStatusBadge(org.status);
const statusBadge = getOrganisationStatusBadge(org);
return (
<tr key={org.id} className="hover:bg-slate-800/40 transition-colors">
<td className="py-3.5 px-5">