Skip to content

Commit f1dcb8a

Browse files
Mfa loader (#928)
Display a loader when posture checks are being evaluated to avoid blinking MFA screen.
1 parent 8a3d399 commit f1dcb8a

5 files changed

Lines changed: 74 additions & 4 deletions

File tree

new-ui/src/shared/components/LocationCard/hooks/useMfaConnect.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,25 @@ type MfaErrorResponse = {
2424

2525
type CodeMfaStartMethod = Extract<MfaStartMethod, 0 | 1>;
2626

27-
export const useMfaConnect = (method: CodeMfaStartMethod) => {
27+
type UseMfaConnectOptions = {
28+
debounceMs?: number;
29+
};
30+
31+
const waitForMinimumDuration = async (startedAt: number, minimumMs: number) => {
32+
const remainingMs = Math.max(minimumMs - (performance.now() - startedAt), 0);
33+
if (remainingMs === 0) return;
34+
35+
await new Promise((resolve) => window.setTimeout(resolve, remainingMs));
36+
};
37+
38+
export const useMfaConnect = (
39+
method: CodeMfaStartMethod,
40+
{ debounceMs = 0 }: UseMfaConnectOptions = {},
41+
) => {
2842
const { location, setPostureError, setView } = useLocationCardContext();
2943

3044
const [token, setToken] = useState<string | null>(null);
31-
const [isStarting, setIsStarting] = useState(false);
45+
const [isStarting, setIsStarting] = useState(debounceMs > 0);
3246
const [startError, setStartError] = useState<string | null>(null);
3347
const [isVerifying, setIsVerifying] = useState(false);
3448
const [verifyError, setVerifyError] = useState<string | null>(null);
@@ -55,7 +69,9 @@ export const useMfaConnect = (method: CodeMfaStartMethod) => {
5569
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional one-shot trigger via startCalled ref
5670
useEffect(() => {
5771
if (!instance || startCalled.current) return;
72+
5873
startCalled.current = true;
74+
const startedAt = performance.now();
5975

6076
setIsStarting(true);
6177

@@ -66,9 +82,11 @@ export const useMfaConnect = (method: CodeMfaStartMethod) => {
6682
location,
6783
method,
6884
});
85+
await waitForMinimumDuration(startedAt, debounceMs);
6986
setRequestHeaders(headers);
7087
setToken(response.token);
7188
} catch (err) {
89+
await waitForMinimumDuration(startedAt, debounceMs);
7290
if (handleMfaStartError({ err, location, setPostureError, setView })) {
7391
return;
7492
}

new-ui/src/shared/components/LocationCard/views/LocationCardMfaEmailView/LocationCardMfaEmailView.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ import { LocationViewHeader } from '../../components/LocationViewHeader/Location
1515
import { useLocationCardContext } from '../../context/context';
1616
import { LocationCardViews } from '../../context/types';
1717
import { useMfaConnect } from '../../hooks/useMfaConnect';
18+
import { LocationCardMfaStartLoader } from '../LocationCardMfaStartLoader/LocationCardMfaStartLoader';
19+
20+
const MIN_POSTURE_LOADER_MS = 500;
1821

1922
export const LocationCardMfaEmailView = () => {
20-
const { setView } = useLocationCardContext();
23+
const { setView, location } = useLocationCardContext();
2124
const { verifyCode, isVerifying, verifyError, isStarting, startError } = useMfaConnect(
2225
MfaStartMethod.Email,
26+
{
27+
debounceMs: location.posture_check_required ? MIN_POSTURE_LOADER_MS : 0,
28+
},
2329
);
2430

2531
const [emailCode, setEmailCode] = useState<string | null>(null);
@@ -47,6 +53,11 @@ export const LocationCardMfaEmailView = () => {
4753
if (verifyError) setError(verifyError);
4854
}, [verifyError]);
4955

56+
// Show loader when posture is being evaluated
57+
const showLoader = location.posture_check_required && isStarting && !startError;
58+
if (showLoader) {
59+
return <LocationCardMfaStartLoader />;
60+
}
5061
return (
5162
<div
5263
className="location-card-mfa-email-view"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import './style.scss';
2+
import { ThemeSpacing } from '../../../../types';
3+
import { Divider } from '../../../Divider/Divider';
4+
import { LoaderSpinner } from '../../../LoaderSpinner/LoaderSpinner';
5+
6+
export const LocationCardMfaStartLoader = () => (
7+
<div className="mfa-start-loader">
8+
<Divider spacing={ThemeSpacing.Md} />
9+
<div className="loader-content">
10+
<LoaderSpinner variant="primary" size={32} />
11+
<p>Checking device requirements...</p>
12+
</div>
13+
</div>
14+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.mfa-start-loader {
2+
> .loader-content {
3+
display: flex;
4+
min-height: 140px;
5+
flex-direction: column;
6+
align-items: center;
7+
justify-content: center;
8+
gap: var(--spacing-md);
9+
10+
p {
11+
font: var(--t-body-xs-500);
12+
color: var(--fg-white-70);
13+
}
14+
}
15+
}

new-ui/src/shared/components/LocationCard/views/LocationCardMfaTotpView/LocationCardMfaTotpView.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ import { LocationViewHeader } from '../../components/LocationViewHeader/Location
1515
import { useLocationCardContext } from '../../context/context';
1616
import { LocationCardViews } from '../../context/types';
1717
import { useMfaConnect } from '../../hooks/useMfaConnect';
18+
import { LocationCardMfaStartLoader } from '../LocationCardMfaStartLoader/LocationCardMfaStartLoader';
19+
20+
const MIN_POSTURE_LOADER_MS = 500;
1821

1922
export const LocationCardMfaTotpView = () => {
20-
const { setView } = useLocationCardContext();
23+
const { setView, location } = useLocationCardContext();
2124
const { verifyCode, isVerifying, verifyError, isStarting, startError } = useMfaConnect(
2225
MfaStartMethod.Totp,
26+
{
27+
debounceMs: location.posture_check_required ? MIN_POSTURE_LOADER_MS : 0,
28+
},
2329
);
2430

2531
const [totpCode, setTotpCode] = useState<string | null>(null);
@@ -47,6 +53,12 @@ export const LocationCardMfaTotpView = () => {
4753
if (verifyError) setError(verifyError);
4854
}, [verifyError]);
4955

56+
// Show loader when posture is being evaluated
57+
const showLoader = location.posture_check_required && isStarting && !startError;
58+
if (showLoader) {
59+
return <LocationCardMfaStartLoader />;
60+
}
61+
5062
return (
5163
<div
5264
className="location-card-mfa-totp-view"

0 commit comments

Comments
 (0)