Refine MFA setup modal guidance (#430)

This commit is contained in:
shenlan 2025-10-06 18:46:53 +08:00 committed by GitHub
parent 72bd7920b2
commit 86d91ab141
2 changed files with 116 additions and 63 deletions

View File

@ -368,6 +368,8 @@ export default function MfaSetupPanel() {
{isDisabling ? copy.disable.confirming : copy.disable.action}
</button>
</div>
{error ? <p className="text-sm text-red-600">{error}</p> : null}
</div>
) : (
<div className="space-y-5">
@ -375,75 +377,102 @@ export default function MfaSetupPanel() {
{hasPendingMfa ? copy.pendingHint : copy.subtitle}
</p>
<div className="space-y-3 rounded-lg border border-gray-200 bg-white p-4">
{qrImage ? (
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-purple-600">{copy.qrLabel}</p>
<div className="mt-2 flex justify-center">
<img
src={qrImage}
alt="Authenticator QR code"
className="h-40 w-40 rounded-lg border border-purple-100 bg-white p-2 shadow-sm"
/>
<ol className="space-y-4">
<li className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm">
<h4 className="text-sm font-semibold text-gray-900">{copy.guide.step1Title}</h4>
<p className="mt-2 text-sm text-gray-600">{copy.guide.step1Description}</p>
<ul className="mt-3 list-disc space-y-1 pl-5 text-sm text-gray-600">
<li>{copy.guide.step1Ios}</li>
<li>{copy.guide.step1Android}</li>
</ul>
</li>
<li className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm">
<h4 className="text-sm font-semibold text-gray-900">{copy.guide.step2Title}</h4>
<p className="mt-2 text-sm text-gray-600">{copy.guide.step2Description}</p>
<div className="mt-4 flex flex-col gap-6 lg:flex-row lg:items-start">
{qrImage ? (
<div className="flex justify-center lg:w-60 lg:justify-start">
<div className="rounded-xl border border-purple-100 bg-purple-50 p-3">
<img
src={qrImage}
alt="Authenticator QR code"
className="h-44 w-44 rounded-lg border border-purple-200 bg-white p-2 shadow-sm"
/>
</div>
</div>
) : null}
<div className="flex-1 space-y-3">
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-purple-600">{copy.secretLabel}</p>
<code className="mt-1 block break-all rounded bg-purple-50 px-3 py-2 text-sm text-purple-700">{secret}</code>
</div>
{uri ? (
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-purple-600">{copy.uriLabel}</p>
<a
href={uri}
className="mt-1 block break-all text-sm text-purple-600 underline"
target="_blank"
rel="noreferrer"
>
{uri}
</a>
</div>
) : null}
<p className="text-xs text-gray-500">{copy.manualHint}</p>
<button
type="button"
onClick={handleProvision}
disabled={isProvisioning}
className="inline-flex items-center justify-center rounded-md border border-purple-200 px-3 py-2 text-xs font-medium text-purple-600 transition hover:border-purple-300 hover:bg-purple-50 disabled:cursor-not-allowed disabled:opacity-70"
>
{isProvisioning ? `${copy.regenerate}` : copy.regenerate}
</button>
</div>
</div>
) : null}
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-purple-600">{copy.secretLabel}</p>
<code className="mt-1 block break-all rounded bg-purple-50 px-3 py-2 text-sm text-purple-700">{secret}</code>
</div>
{uri ? (
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-purple-600">{copy.uriLabel}</p>
<a
href={uri}
className="mt-1 block break-all text-sm text-purple-600 underline"
target="_blank"
rel="noreferrer"
>
{uri}
</a>
</div>
) : null}
<p className="text-xs text-gray-500">{copy.manualHint}</p>
<button
type="button"
onClick={handleProvision}
disabled={isProvisioning}
className="inline-flex items-center justify-center rounded-md border border-purple-200 px-3 py-2 text-xs font-medium text-purple-600 transition hover:border-purple-300 hover:bg-purple-50 disabled:cursor-not-allowed disabled:opacity-70"
>
{isProvisioning ? `${copy.regenerate}` : copy.regenerate}
</button>
</div>
</li>
<form onSubmit={handleVerify} className="space-y-3">
<label className="block text-sm font-medium text-gray-700" htmlFor="mfa-code">
{copy.codeLabel}
</label>
<input
id="mfa-code"
name="code"
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={code}
onChange={(event) => setCode(event.target.value)}
placeholder={copy.codePlaceholder}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-gray-900 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-200"
/>
<button
type="submit"
disabled={isVerifying}
className="inline-flex items-center justify-center rounded-md bg-purple-600 px-4 py-2 text-sm font-medium text-white shadow transition hover:bg-purple-500 disabled:cursor-not-allowed disabled:opacity-70"
>
{isVerifying ? copy.verifying : copy.verify}
</button>
</form>
<li className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm">
<h4 className="text-sm font-semibold text-gray-900">{copy.guide.step3Title}</h4>
<p className="mt-2 text-sm text-gray-600">{copy.guide.step3Description}</p>
<form
onSubmit={handleVerify}
className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-end sm:gap-4"
>
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700" htmlFor="mfa-code">
{copy.codeLabel}
</label>
<input
id="mfa-code"
name="code"
type="text"
inputMode="numeric"
pattern="[0-9]*"
maxLength={6}
autoComplete="one-time-code"
value={code}
onChange={(event) => setCode(event.target.value)}
placeholder={copy.codePlaceholder}
className="mt-2 w-full rounded-lg border border-gray-300 px-4 py-3 text-center text-2xl font-mono tracking-[0.6em] text-gray-900 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-200"
/>
</div>
<button
type="submit"
disabled={isVerifying}
className="inline-flex items-center justify-center rounded-md bg-purple-600 px-5 py-2.5 text-sm font-medium text-white shadow transition hover:bg-purple-500 disabled:cursor-not-allowed disabled:opacity-70"
>
{isVerifying ? copy.verifying : copy.verify}
</button>
</form>
</li>
</ol>
{error ? <p className="text-sm text-red-600">{error}</p> : null}
</div>
)}
{error ? <p className="text-sm text-red-600">{error}</p> : null}
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4 text-xs text-gray-600">
<p className="font-semibold text-gray-700">{copy.actions.help}</p>
<p className="mt-1 text-gray-600">{copy.actions.description}</p>

View File

@ -683,6 +683,20 @@ export const translations: Record<'en' | 'zh', Translation> = {
verifying: 'Verifying…',
successTitle: 'Authenticator connected',
successBody: 'Your account now requires an authenticator code at sign-in.',
guide: {
step1Title: '1. Install an authenticator app',
step1Description:
'Download Alibaba Cloud Authenticator or Google Authenticator on your phone to get started.',
step1Ios:
'iOS: Search for “Google Authenticator” or “Alibaba Cloud Authenticator” in the App Store and install it.',
step1Android:
'Android: Search for “Google Authenticator” or “Alibaba Cloud Authenticator” in Google Play and install it.',
step2Title: '2. Scan the QR code to bind Google Authenticator',
step2Description:
'Open the authenticator app and scan this QR code. Unable to scan? Enter the secret key manually.',
step3Title: '3. Enter the verification code to finish',
step3Description: 'Enter the 6-digit code generated by the authenticator app to complete binding.',
},
status: {
issuedAt: 'Key generated at',
confirmedAt: 'Enabled at',
@ -1033,6 +1047,16 @@ export const translations: Record<'en' | 'zh', Translation> = {
verifying: '验证中…',
successTitle: '认证器绑定成功',
successBody: '以后登录时将需要动态验证码,账号更安全。',
guide: {
step1Title: '1 请在手机端下载阿里云 APP 或 Google Authenticator 身份验证器',
step1Description: '下载并安装验证器应用,准备开始绑定流程。',
step1Ios: '苹果:在 App Store 搜索 “Google Authenticator” 或 “阿里云 身份验证器” 并安装。',
step1Android: '安卓:在应用商店搜索 “Google Authenticator” 或 “阿里云 身份验证器” 并安装。',
step2Title: '2 使用阿里云或 Google Authenticator 身份验证器获取验证码',
step2Description: '打开验证器扫描下方二维码,如无法扫描可手动输入密钥。',
step3Title: '3 输入验证码完成绑定',
step3Description: '在验证器中查看 6 位验证码并输入完成绑定。',
},
status: {
issuedAt: '密钥生成时间',
confirmedAt: '启用时间',