refine mfa setup prompts (#592)
This commit is contained in:
parent
1b2cee4cf6
commit
a67bee470e
@ -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}
|
||||
|
||||
|
||||
@ -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: '管理多因素认证',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user