MFA Recovery codes: UI tweaks (#50488)

## What kind of change does this PR introduce?

Admonition is not the right UI to tell users how many are still
available.

## What is the current behavior?

No recovery codes yet:

<img width="724" height="499" alt="image"
src="https://github.com/user-attachments/assets/db9d47af-3a81-42d2-8cf0-9302816ceb21"
/>

After:
<img width="758" height="525" alt="image"
src="https://github.com/user-attachments/assets/68acc4bf-372f-4472-a3e4-a8263a8993d0"
/>

## What is the new behavior?

No recovery codes yet:
<img width="720" height="556" alt="image"
src="https://github.com/user-attachments/assets/48ce08a7-9650-428b-be5d-b8bb7ef5b720"
/>

After:
<img width="720" height="541" alt="image"
src="https://github.com/user-attachments/assets/35a8698d-9a6f-44cb-91c8-2ddb8d0f3a7b"
/>

When low number of codes available:
<img width="733" height="548" alt="image"
src="https://github.com/user-attachments/assets/d60017ba-ada6-47bb-9f83-a2a65674f800"
/>



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Improvements**
- Recovery codes now appear in a dedicated section when enabled,
separate from multi-factor authentication settings.
- Recovery-code status updates are announced to screen readers for
improved accessibility.
- Available recovery codes are displayed in a clearer card-based layout
once status information is available.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-09-17 09:36:19 +02:00
1 parent 7ab3f32625
commit c2d8b08299
1 file changed
+36 -15
@@ -2,7 +2,7 @@ import { useFlag } from 'common'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { useState } from 'react'
import { Button, Card, CardContent } from 'ui'
import { Button, Card, CardContent, cn } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import {
PageSection,
@@ -44,6 +44,41 @@ export const TOTPFactors = () => {
return (
<>
{enableAuthRecoveryCodes && (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Recovery codes</PageSectionTitle>
<PageSectionDescription>
Recovery codes allow you to recover your account in case you lost access to your MFA
apps.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent aria-live="polite">
{recoveryCodesStatus?.status === 'unenrolled' && <GenerateRecoveryCodesModal />}
{recoveryCodesStatus?.status === 'available' && recoveryCodesStatus?.data && (
<Card>
<CardContent className="flex flex-col gap-2">
<p
className={cn(
'text-sm',
recoveryCodesStatus.data.remaining < 2 ? 'text-warning' : ''
)}
>
{recoveryCodesStatus.data.remaining}/{recoveryCodesStatus.data.total} recovery
codes available
</p>
<div className="flex gap-2 ml-auto">
<RegenerateRecoveryCodesModal />
{IS_STAGING_OR_LOCAL && <UnenrollRecoveryCodesModal />}
</div>
</CardContent>
</Card>
)}
</PageSectionContent>
</PageSection>
)}
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
@@ -62,20 +97,6 @@ export const TOTPFactors = () => {
)}
</PageSectionMeta>
<PageSectionContent className="flex flex-col gap-4">
{recoveryCodesStatus?.status === 'unenrolled' && <GenerateRecoveryCodesModal />}
{recoveryCodesStatus?.status === 'available' && (
<Admonition
layout="responsive"
title={`${recoveryCodesStatus?.data?.remaining}/${recoveryCodesStatus?.data?.total} recovery codes available`}
description="Recovery codes allow you to recover your account in case you lost access to your MFA apps."
actions={
<div className="flex flex-col gap-2">
<RegenerateRecoveryCodesModal />
{IS_STAGING_OR_LOCAL && <UnenrollRecoveryCodesModal />}
</div>
}
/>
)}
{shouldShowLockoutWarning && (
<Admonition
type="danger"