refine mfa setup prompts (#592)

This commit is contained in:
shenlan 2025-10-27 12:33:26 +08:00 committed by GitHub
parent 1b2cee4cf6
commit a67bee470e
4 changed files with 112 additions and 74 deletions

View File

@ -87,6 +87,16 @@ export default function Sidebar({ className = '', onNavigate }: SidebarProps) {
.filter((value): value is NavSection => Boolean(value))
}, [requiresSetup, user])
const pendingHint = copy.pendingHint.trim()
const lockedMessage = copy.lockedMessage.trim()
const setupLabel = copy.actions.setup.trim()
const docsLabel = copy.actions.docs.trim()
const hasBannerContent =
pendingHint.length > 0 ||
lockedMessage.length > 0 ||
setupLabel.length > 0 ||
docsLabel.length > 0
return (
<aside
className={`flex h-full w-64 flex-col gap-6 border-r border-[color:var(--color-surface-border)] bg-[var(--color-surface-elevated)] p-6 text-[var(--color-text)] shadow-[var(--shadow-md)] backdrop-blur transition-colors ${className}`}
@ -97,27 +107,33 @@ export default function Sidebar({ className = '', onNavigate }: SidebarProps) {
<p className="text-sm text-[var(--color-text-subtle)]"></p>
</div>
{requiresSetup ? (
{requiresSetup && hasBannerContent ? (
<div className="rounded-[var(--radius-lg)] border border-[color:var(--color-warning-muted)] bg-[var(--color-warning-muted)] p-3 text-xs text-[var(--color-warning-foreground)] transition-colors">
<p className="font-semibold">{copy.pendingHint}</p>
<p className="mt-1">{copy.lockedMessage}</p>
<div className="mt-2 flex flex-wrap gap-2">
<Link
href="/panel/account?setupMfa=1"
onClick={onNavigate}
className="inline-flex items-center justify-center rounded-md bg-[var(--color-primary)] px-3 py-1.5 text-xs font-medium text-[var(--color-primary-foreground)] shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--color-primary-hover)]"
>
{copy.actions.setup}
</Link>
<a
href={copy.actions.docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-[color:var(--color-primary-border)] px-3 py-1.5 text-xs font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
>
{copy.actions.docs}
</a>
</div>
{pendingHint.length > 0 ? <p className="font-semibold">{pendingHint}</p> : null}
{lockedMessage.length > 0 ? <p className="mt-1">{lockedMessage}</p> : null}
{setupLabel.length > 0 || docsLabel.length > 0 ? (
<div className="mt-2 flex flex-wrap gap-2">
{setupLabel.length > 0 ? (
<Link
href="/panel/account?setupMfa=1"
onClick={onNavigate}
className="inline-flex items-center justify-center rounded-md bg-[var(--color-primary)] px-3 py-1.5 text-xs font-medium text-[var(--color-primary-foreground)] shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--color-primary-hover)]"
>
{setupLabel}
</Link>
) : null}
{docsLabel.length > 0 ? (
<a
href={copy.actions.docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-[color:var(--color-primary-border)] px-3 py-1.5 text-xs font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
>
{docsLabel}
</a>
) : null}
</div>
) : null}
</div>
) : null}

View File

@ -765,10 +765,10 @@ export const translations: Record<'en' | 'zh', Translation> = {
uuidNote: 'Your UUID uniquely identifies you across XControl services.',
lockBanner: {
title: 'Finish MFA setup',
body: 'Complete multi-factor authentication to unlock every panel section.',
action: 'Set up MFA',
docs: 'View setup guide',
logout: 'Sign out',
body: 'Enable multi-factor authentication to strengthen your account security.',
action: '',
docs: '',
logout: '',
},
cards: {
uuid: {
@ -811,7 +811,7 @@ export const translations: Record<'en' | 'zh', Translation> = {
mfa: {
title: 'Multi-factor authentication',
subtitle: 'Bind Google Authenticator to finish securing your account.',
pendingHint: 'Complete this step to unlock the user center and other console features.',
pendingHint: 'Enable multi-factor authentication to safeguard your account.',
enabledHint: 'Authenticator codes are now required for every sign-in.',
summary: {
description: 'View your authenticator status and manage binding without leaving the dashboard.',
@ -856,7 +856,7 @@ export const translations: Record<'en' | 'zh', Translation> = {
disabled: 'Not enabled',
},
qrLabel: 'Authenticator QR code',
lockedMessage: 'Finish the binding flow before exploring other sections.',
lockedMessage: 'Complete the setup to keep your account protected.',
steps: {
intro: 'Complete these two steps to secure your account:',
provision: '1. Generate a secret and scan the QR code with Google Authenticator.',
@ -864,11 +864,11 @@ export const translations: Record<'en' | 'zh', Translation> = {
},
actions: {
help: 'Need help staying secure?',
description: 'If you run into issues, sign out or review the setup documentation.',
description: 'If you run into issues, try signing out and starting again.',
logout: 'Sign out',
docs: 'View setup guide',
docs: '',
docsUrl: '/docs/account-service-configuration/latest',
setup: 'Resume setup',
setup: '',
},
modal: {
title: 'Manage multi-factor authentication',
@ -1353,10 +1353,10 @@ export const translations: Record<'en' | 'zh', Translation> = {
uuidNote: 'UUID 是你在 XControl 中的唯一身份凭证,后续的所有服务都与它关联在一起。',
lockBanner: {
title: '完成多因素认证',
body: '完成 MFA 绑定后即可访问所有控制台板块。',
action: '立即设置',
docs: '查看操作指引',
logout: '退出登录',
body: '启用多因素认证保护你的账号安全。',
action: '',
docs: '',
logout: '',
},
cards: {
uuid: {
@ -1399,7 +1399,7 @@ export const translations: Record<'en' | 'zh', Translation> = {
mfa: {
title: '多因素认证',
subtitle: '绑定 Google Authenticator完成账号安全校验。',
pendingHint: '启用多因素认证后即可访问用户中心和更多控制台功能。',
pendingHint: '启用多因素认证以提升账号安全。',
enabledHint: '以后登录都需要输入动态验证码。',
summary: {
description: '在此查看当前绑定状态,并随时完成认证器的绑定或解绑。',
@ -1440,7 +1440,7 @@ export const translations: Record<'en' | 'zh', Translation> = {
disabled: '未开启',
},
qrLabel: '认证二维码',
lockedMessage: '请先完成绑定流程,再访问其他板块。',
lockedMessage: '完成绑定即可持续守护账号安全。',
steps: {
intro: '按照以下两步完成账号安全加固:',
provision: '1. 生成密钥并在认证器中扫描二维码。',
@ -1448,11 +1448,11 @@ export const translations: Record<'en' | 'zh', Translation> = {
},
actions: {
help: '需要帮助?',
description: '遇到问题时可以退出重新登录,或查看绑定指引。',
description: '遇到问题时可以退出重新登录,再次尝试。',
logout: '退出登录',
docs: '查看操作指引',
docs: '',
docsUrl: '/docs/account-service-configuration/latest',
setup: '继续设置',
setup: '',
},
modal: {
title: '管理多因素认证',

View File

@ -103,6 +103,9 @@ export default function MfaSetupPanel() {
const searchParams = useSearchParams()
const { user, refresh, logout } = useUser()
const lockedMessage = copy.lockedMessage.trim()
const actionDocs = copy.actions.docs.trim()
const [status, setStatus] = useState<TotpStatus | null>(null)
const [secret, setSecret] = useState('')
const [uri, setUri] = useState('')
@ -477,8 +480,8 @@ export default function MfaSetupPanel() {
>
{displayStatus?.totpEnabled ? copy.summary.manage : copy.summary.bind}
</button>
{requiresSetup ? (
<p className="text-xs text-[var(--color-warning-foreground)]">{copy.lockedMessage}</p>
{requiresSetup && lockedMessage.length > 0 ? (
<p className="text-xs text-[var(--color-warning-foreground)]">{lockedMessage}</p>
) : null}
</div>
</div>
@ -548,7 +551,7 @@ export default function MfaSetupPanel() {
) : (
<div className="space-y-5">
<p className="rounded-lg border border-[color:var(--color-warning-muted)] bg-[var(--color-warning-muted)] p-3 text-sm text-[var(--color-warning-foreground)]">
{hasPendingMfa ? copy.lockedMessage : copy.subtitle}
{hasPendingMfa ? (lockedMessage.length > 0 ? lockedMessage : copy.subtitle) : copy.subtitle}
</p>
{lockoutActive ? (
@ -687,14 +690,16 @@ export default function MfaSetupPanel() {
>
{copy.actions.logout}
</button>
<a
href={copy.actions.docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-transparent bg-[var(--color-primary)] px-3 py-2 text-xs font-medium text-[var(--color-primary-foreground)] transition hover:bg-[var(--color-primary-hover)]"
>
{copy.actions.docs}
</a>
{actionDocs.length > 0 ? (
<a
href={copy.actions.docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-transparent bg-[var(--color-primary)] px-3 py-2 text-xs font-medium text-[var(--color-primary-foreground)] transition hover:bg-[var(--color-primary-hover)]"
>
{actionDocs}
</a>
) : null}
</div>
</div>
</div>

View File

@ -98,6 +98,15 @@ export default function UserOverview() {
router.refresh()
}, [logout, router])
const lockBannerBody = copy.lockBanner.body.trim()
const lockBannerAction = copy.lockBanner.action.trim()
const lockBannerDocs = copy.lockBanner.docs.trim()
const lockBannerLogout = copy.lockBanner.logout.trim()
const hasLockBannerActions =
lockBannerAction.length > 0 ||
lockBannerDocs.length > 0 ||
lockBannerLogout.length > 0
return (
<div className="space-y-6 text-[var(--color-text)] transition-colors">
<div>
@ -115,31 +124,39 @@ export default function UserOverview() {
{requiresSetup ? (
<div className="rounded-[var(--radius-xl)] border border-[color:var(--color-warning-muted)] bg-[var(--color-warning-muted)] p-4 text-sm text-[var(--color-warning-foreground)] transition-colors">
<p className="text-base font-semibold">{copy.lockBanner.title}</p>
<p className="mt-1 text-sm">{copy.lockBanner.body}</p>
<div className="mt-3 flex flex-wrap gap-2 text-xs">
<button
type="button"
onClick={handleGoToSetup}
className="inline-flex items-center justify-center rounded-md bg-[var(--color-primary)] px-4 py-2 text-sm font-medium text-[var(--color-primary-foreground)] shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--color-primary-hover)]"
>
{copy.lockBanner.action}
</button>
<a
href={docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-[color:var(--color-primary-border)] px-4 py-2 text-sm font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
>
{copy.lockBanner.docs}
</a>
<button
type="button"
onClick={handleLogout}
className="inline-flex items-center justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-[var(--color-warning-foreground)] transition-colors hover:bg-[var(--color-warning-muted)]"
>
{copy.lockBanner.logout}
</button>
</div>
{lockBannerBody.length > 0 ? <p className="mt-1 text-sm">{lockBannerBody}</p> : null}
{hasLockBannerActions ? (
<div className="mt-3 flex flex-wrap gap-2 text-xs">
{lockBannerAction.length > 0 ? (
<button
type="button"
onClick={handleGoToSetup}
className="inline-flex items-center justify-center rounded-md bg-[var(--color-primary)] px-4 py-2 text-sm font-medium text-[var(--color-primary-foreground)] shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--color-primary-hover)]"
>
{lockBannerAction}
</button>
) : null}
{lockBannerDocs.length > 0 ? (
<a
href={docsUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-md border border-[color:var(--color-primary-border)] px-4 py-2 text-sm font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
>
{lockBannerDocs}
</a>
) : null}
{lockBannerLogout.length > 0 ? (
<button
type="button"
onClick={handleLogout}
className="inline-flex items-center justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-[var(--color-warning-foreground)] transition-colors hover:bg-[var(--color-warning-muted)]"
>
{lockBannerLogout}
</button>
) : null}
</div>
) : null}
</div>
) : null}