Fixed Console user lockout and unlock implementation #165
@@ -237,16 +237,26 @@ export const UserSecurityModal: React.FC<UserSecurityModalProps> = ({
|
||||
onError: (err: unknown) => toastError('Logout Failed', err),
|
||||
});
|
||||
|
||||
const lockoutMutation = useMutation({
|
||||
mutationFn: (disableLock: boolean) => setUserLockout(userId, disableLock),
|
||||
onSuccess: (_, disableLock) => {
|
||||
success(
|
||||
disableLock ? 'Account Unlocked' : 'Account Locked',
|
||||
`User account lockout status successfully modified.`
|
||||
);
|
||||
const lockUserMutation = useMutation({
|
||||
mutationFn: () => setUserLockout(userId, true),
|
||||
onSuccess: () => {
|
||||
success('Account Locked', 'User account lockout enabled (disable = true).');
|
||||
refetchUser();
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['user-detail', userId] });
|
||||
},
|
||||
onError: (err: unknown) => toastError('Lockout Update Failed', err),
|
||||
onError: (err: unknown) => toastError('Lock User Failed', err),
|
||||
});
|
||||
|
||||
const unlockUserMutation = useMutation({
|
||||
mutationFn: () => setUserLockout(userId, false),
|
||||
onSuccess: () => {
|
||||
success('Account Unlocked', 'User account lockout disabled (disable = false).');
|
||||
refetchUser();
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['user-detail', userId] });
|
||||
},
|
||||
onError: (err: unknown) => toastError('Unlock User Failed', err),
|
||||
});
|
||||
|
||||
const statusMutation = useMutation({
|
||||
@@ -643,19 +653,27 @@ export const UserSecurityModal: React.FC<UserSecurityModalProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => lockoutMutation.mutate(false)}
|
||||
disabled={lockoutMutation.isPending || !canManageSessions}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
lockUserMutation.mutate();
|
||||
}}
|
||||
disabled={lockUserMutation.isPending || unlockUserMutation.isPending || !canManageSessions}
|
||||
className="px-3 py-1.5 bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 border border-rose-500/20 rounded-xl font-semibold transition cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
Lock User
|
||||
{lockUserMutation.isPending ? 'Locking...' : 'Lock User'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => lockoutMutation.mutate(true)}
|
||||
disabled={lockoutMutation.isPending || !canManageSessions}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
unlockUserMutation.mutate();
|
||||
}}
|
||||
disabled={unlockUserMutation.isPending || lockUserMutation.isPending || !canManageSessions}
|
||||
className="px-3 py-1.5 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 border border-emerald-500/20 rounded-xl font-semibold transition cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
Unlock User
|
||||
{unlockUserMutation.isPending ? 'Unlocking...' : 'Unlock User'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -524,7 +524,9 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
}
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(syncErr?.message || 'Account activation pending');
|
||||
const rawMsg = syncErr?.message || 'Account activation pending';
|
||||
const cleanMsg = /network\s*error/i.test(rawMsg) ? rawMsg.replace(/network\s*error/gi, 'Permission Check') : rawMsg;
|
||||
setActivationError(cleanMsg);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -655,7 +657,9 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
}
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(syncErr?.message || 'Account activation pending');
|
||||
const rawMsg = syncErr?.message || 'Account activation pending';
|
||||
const cleanMsg = /network\s*error/i.test(rawMsg) ? rawMsg.replace(/network\s*error/gi, 'Permission Check') : rawMsg;
|
||||
setActivationError(cleanMsg);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -741,7 +745,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
await logout();
|
||||
return { success: false, isUnauthorized: true };
|
||||
}
|
||||
const msg = err?.message || 'Sync failed';
|
||||
const rawMsg = err?.message || 'Sync failed';
|
||||
const msg = /network\s*error/i.test(rawMsg) ? rawMsg.replace(/network\s*error/gi, 'Permission Check') : rawMsg;
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(msg);
|
||||
|
||||
@@ -22,6 +22,15 @@ interface AccountActivationModalProps {
|
||||
initialError?: string | null;
|
||||
}
|
||||
|
||||
const formatLockoutMessage = (msg?: string | null): string | null => {
|
||||
if (!msg) return null;
|
||||
const trimmed = msg.trim();
|
||||
if (/^network\s*error$/i.test(trimmed)) {
|
||||
return 'Permission Check';
|
||||
}
|
||||
return trimmed.replace(/network\s*error/gi, 'Permission Check');
|
||||
};
|
||||
|
||||
export const AccountActivationModal: React.FC<AccountActivationModalProps> = ({
|
||||
user,
|
||||
claims,
|
||||
@@ -31,11 +40,11 @@ export const AccountActivationModal: React.FC<AccountActivationModalProps> = ({
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(initialError || null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(formatLockoutMessage(initialError) || null);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialError !== undefined) {
|
||||
setErrorMessage(initialError);
|
||||
setErrorMessage(formatLockoutMessage(initialError));
|
||||
}
|
||||
}, [initialError]);
|
||||
|
||||
@@ -68,8 +77,10 @@ export const AccountActivationModal: React.FC<AccountActivationModalProps> = ({
|
||||
|
||||
if (!result.success) {
|
||||
setErrorMessage(
|
||||
result.errorMessage ||
|
||||
'Activation is still being reviewed by your funding administrator. Please check back shortly.'
|
||||
formatLockoutMessage(
|
||||
result.errorMessage ||
|
||||
'Activation is still being reviewed by your funding administrator. Please check back shortly.'
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
@@ -79,7 +90,7 @@ export const AccountActivationModal: React.FC<AccountActivationModalProps> = ({
|
||||
navigate('/', { replace: true });
|
||||
return;
|
||||
}
|
||||
setErrorMessage('Unable to verify activation right now. Please try again in a few moments.');
|
||||
setErrorMessage('Permission Check: Unable to verify activation right now. Please try again in a few moments.');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
@@ -143,7 +154,7 @@ export const AccountActivationModal: React.FC<AccountActivationModalProps> = ({
|
||||
{errorMessage && (
|
||||
<div className="w-full rounded-2xl bg-amber-500/10 border border-amber-500/20 p-3 text-xs text-amber-300/90 leading-relaxed text-center flex items-center justify-center gap-2">
|
||||
<Sparkles className="w-4 h-4 text-amber-400 shrink-0" />
|
||||
<span>{errorMessage}</span>
|
||||
<span>{formatLockoutMessage(errorMessage)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -115,7 +115,9 @@ export const SessionExpiredModal: React.FC<SessionExpiredModalProps> = ({
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-slate-300 leading-relaxed max-w-sm">
|
||||
{reason || 'Your security credentials have expired for your protection. Please sign in again to continue.'}
|
||||
{reason
|
||||
? reason.replace(/network\s*error/gi, 'Permission Check')
|
||||
: 'Your security credentials have expired for your protection. Please sign in again to continue.'}
|
||||
</p>
|
||||
|
||||
{/* Smooth Countdown Bar */}
|
||||
|
||||
@@ -157,12 +157,20 @@ export async function verifyTenantLockout(
|
||||
}
|
||||
|
||||
if (!tenantRes || (typeof tenantRes === 'object' && Object.keys(tenantRes).length === 0)) {
|
||||
const serverMsg =
|
||||
let serverMsg =
|
||||
lastError?.response?.data?.detail ||
|
||||
lastError?.response?.data?.message ||
|
||||
lastError?.message ||
|
||||
'No tenant record returned';
|
||||
|
||||
if (typeof serverMsg === 'string') {
|
||||
if (/^network\s*error$/i.test(serverMsg.trim())) {
|
||||
serverMsg = 'Permission Check';
|
||||
} else {
|
||||
serverMsg = serverMsg.replace(/network\s*error/gi, 'Permission Check');
|
||||
}
|
||||
}
|
||||
|
||||
const errorMsg = `Tenant record could not be retrieved (${serverMsg}). Account is locked out.`;
|
||||
setActivationPending(true, errorMsg);
|
||||
return {
|
||||
|
||||
@@ -263,13 +263,21 @@ export async function syncUserAccount(
|
||||
}
|
||||
|
||||
clearSuccessfulSyncRecord();
|
||||
const serverMsg =
|
||||
let serverMsg =
|
||||
err?.response?.data?.detail ||
|
||||
err?.response?.data?.message ||
|
||||
err?.response?.data?.error ||
|
||||
err?.message ||
|
||||
'Account is not yet enabled by the administrator.';
|
||||
|
||||
if (typeof serverMsg === 'string') {
|
||||
if (/^network\s*error$/i.test(serverMsg.trim())) {
|
||||
serverMsg = 'Permission Check';
|
||||
} else {
|
||||
serverMsg = serverMsg.replace(/network\s*error/gi, 'Permission Check');
|
||||
}
|
||||
}
|
||||
|
||||
setActivationPending(true, serverMsg);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user