Update MFA UI flags and lint guidance
This commit is contained in:
parent
e6817ab1e0
commit
9ac93eece5
@ -28,7 +28,8 @@ yarn preview # Build and start production server
|
|||||||
### Code Quality
|
### Code Quality
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn lint # Run ESLint
|
yarn lint # Run ESLint (currently fails under Next 16 CLI; use eslint command below)
|
||||||
|
./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to . # Run ESLint directly (ignore parent configs)
|
||||||
yarn typecheck # TypeScript type checking
|
yarn typecheck # TypeScript type checking
|
||||||
yarn format # Format code with Prettier
|
yarn format # Format code with Prettier
|
||||||
```
|
```
|
||||||
@ -246,6 +247,7 @@ MUST NOT:
|
|||||||
- No @/ imports inside packages
|
- No @/ imports inside packages
|
||||||
- Never "fix" libraries by polluting the app
|
- Never "fix" libraries by polluting the app
|
||||||
- Always run `yarn lint` and `yarn typecheck` before completing tasks
|
- Always run `yarn lint` and `yarn typecheck` before completing tasks
|
||||||
|
- If `yarn lint` fails with "Invalid project directory .../lint" (Next 16 CLI), use `./node_modules/.bin/eslint .` instead
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
Required:
|
Required:
|
||||||
|
|
||||||
- yarn lint
|
- yarn lint (currently fails under Next 16 CLI; use eslint command below)
|
||||||
|
- ./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to .
|
||||||
- yarn typecheck
|
- yarn typecheck
|
||||||
- yarn build
|
- yarn build
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,11 @@ function formatTimestamp(value?: string) {
|
|||||||
return date.toLocaleString()
|
return date.toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MfaSetupPanel() {
|
type MfaSetupPanelProps = {
|
||||||
|
showSummary?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MfaSetupPanel({ showSummary = true }: MfaSetupPanelProps) {
|
||||||
const { language } = useLanguage()
|
const { language } = useLanguage()
|
||||||
const copy = translations[language].userCenter.mfa
|
const copy = translations[language].userCenter.mfa
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -435,6 +439,9 @@ export default function MfaSetupPanel() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
if (!showSummary) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<h2 className="text-xl font-semibold text-[var(--color-text)]">{copy.title}</h2>
|
<h2 className="text-xl font-semibold text-[var(--color-text)]">{copy.title}</h2>
|
||||||
@ -451,42 +458,44 @@ export default function MfaSetupPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card>
|
{showSummary ? (
|
||||||
<div className="space-y-6">
|
<Card>
|
||||||
<div className="flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between">
|
<div className="space-y-6">
|
||||||
<div>
|
<div className="flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<h2 className="text-xl font-semibold text-[var(--color-text)]">{copy.title}</h2>
|
<div>
|
||||||
<p className="mt-1 text-sm text-[var(--color-text-subtle)]">{copy.summary.description}</p>
|
<h2 className="text-xl font-semibold text-[var(--color-text)]">{copy.title}</h2>
|
||||||
<dl className="mt-4 grid gap-4 text-xs text-[var(--color-text-subtle)] sm:grid-cols-2">
|
<p className="mt-1 text-sm text-[var(--color-text-subtle)]">{copy.summary.description}</p>
|
||||||
<div>
|
<dl className="mt-4 grid gap-4 text-xs text-[var(--color-text-subtle)] sm:grid-cols-2">
|
||||||
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.summary.statusLabel}</dt>
|
<div>
|
||||||
<dd className="mt-1 text-sm text-[var(--color-text)]">{statusLabel}</dd>
|
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.summary.statusLabel}</dt>
|
||||||
</div>
|
<dd className="mt-1 text-sm text-[var(--color-text)]">{statusLabel}</dd>
|
||||||
<div>
|
</div>
|
||||||
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.status.issuedAt}</dt>
|
<div>
|
||||||
<dd className="mt-1 text-sm text-[var(--color-text)]">{formatTimestamp(displayStatus?.totpSecretIssuedAt)}</dd>
|
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.status.issuedAt}</dt>
|
||||||
</div>
|
<dd className="mt-1 text-sm text-[var(--color-text)]">{formatTimestamp(displayStatus?.totpSecretIssuedAt)}</dd>
|
||||||
<div>
|
</div>
|
||||||
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.status.confirmedAt}</dt>
|
<div>
|
||||||
<dd className="mt-1 text-sm text-[var(--color-text)]">{formatTimestamp(displayStatus?.totpConfirmedAt)}</dd>
|
<dt className="font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.status.confirmedAt}</dt>
|
||||||
</div>
|
<dd className="mt-1 text-sm text-[var(--color-text)]">{formatTimestamp(displayStatus?.totpConfirmedAt)}</dd>
|
||||||
</dl>
|
</div>
|
||||||
</div>
|
</dl>
|
||||||
<div className="flex flex-col items-start gap-3 sm:items-end">
|
</div>
|
||||||
<button
|
<div className="flex flex-col items-start gap-3 sm:items-end">
|
||||||
type="button"
|
<button
|
||||||
onClick={openDialog}
|
type="button"
|
||||||
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 hover:bg-[var(--color-primary-hover)]"
|
onClick={openDialog}
|
||||||
>
|
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 hover:bg-[var(--color-primary-hover)]"
|
||||||
{displayStatus?.totpEnabled ? copy.summary.manage : copy.summary.bind}
|
>
|
||||||
</button>
|
{displayStatus?.totpEnabled ? copy.summary.manage : copy.summary.bind}
|
||||||
{requiresSetup ? (
|
</button>
|
||||||
<p className="text-xs text-[var(--color-warning-foreground)]">{copy.lockedMessage}</p>
|
{requiresSetup ? (
|
||||||
) : null}
|
<p className="text-xs text-[var(--color-warning-foreground)]">{copy.lockedMessage}</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Card>
|
) : null}
|
||||||
|
|
||||||
{isDialogOpen ? (
|
{isDialogOpen ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -34,7 +34,11 @@ function resolveDisplayName(
|
|||||||
return user.email
|
return user.email
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserOverview() {
|
type UserOverviewProps = {
|
||||||
|
hideMfaMainPrompt?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserOverview({ hideMfaMainPrompt = false }: UserOverviewProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { language } = useLanguage()
|
const { language } = useLanguage()
|
||||||
const copy = translations[language].userCenter.overview
|
const copy = translations[language].userCenter.overview
|
||||||
@ -114,7 +118,7 @@ export default function UserOverview() {
|
|||||||
<p className="mt-1 text-xs text-[var(--color-text-subtle)] opacity-80">{copy.uuidNote}</p>
|
<p className="mt-1 text-xs text-[var(--color-text-subtle)] opacity-80">{copy.uuidNote}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{requiresSetup ? (
|
{!hideMfaMainPrompt && 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">
|
<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="text-base font-semibold">{copy.lockBanner.title}</p>
|
||||||
<p className="mt-1 text-sm">{copy.lockBanner.body}</p>
|
<p className="mt-1 text-sm">{copy.lockBanner.body}</p>
|
||||||
@ -180,21 +184,24 @@ export default function UserOverview() {
|
|||||||
<p className="mt-3 text-xs text-[var(--color-text-subtle)]">{copy.cards.email.description}</p>
|
<p className="mt-3 text-xs text-[var(--color-text-subtle)]">{copy.cards.email.description}</p>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
{!hideMfaMainPrompt ? (
|
||||||
<div className="flex items-start justify-between gap-4">
|
<Card>
|
||||||
<div>
|
<div className="flex items-start justify-between gap-4">
|
||||||
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.cards.mfa.label}</p>
|
<div>
|
||||||
<p className="mt-1 text-base font-medium text-[var(--color-text)]">{mfaStatusLabel}</p>
|
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.cards.mfa.label}</p>
|
||||||
<p className="mt-3 text-xs text-[var(--color-text-subtle)]">{copy.cards.mfa.description}</p>
|
<p className="mt-1 text-base font-medium text-[var(--color-text)]">{mfaStatusLabel}</p>
|
||||||
|
<p className="mt-3 text-xs text-[var(--color-text-subtle)]">{copy.cards.mfa.description}</p>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/panel/account?setupMfa=1"
|
||||||
|
className="inline-flex items-center justify-center rounded-full border border-[color:var(--color-primary-border)] px-3 py-1 text-xs font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
|
||||||
|
>
|
||||||
|
{copy.cards.mfa.action}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
</Card>
|
||||||
href="/panel/account?setupMfa=1"
|
) : null}
|
||||||
className="inline-flex items-center justify-center rounded-full border border-[color:var(--color-primary-border)] px-3 py-1 text-xs font-medium text-[var(--color-primary)] transition-colors hover:border-[color:var(--color-primary)] hover:bg-[var(--color-primary-muted)]"
|
|
||||||
>
|
|
||||||
{copy.cards.mfa.action}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import UserOverview from '../components/UserOverview'
|
|||||||
export default function UserCenterAccountRoute() {
|
export default function UserCenterAccountRoute() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<UserOverview />
|
<UserOverview hideMfaMainPrompt />
|
||||||
<MfaSetupPanel />
|
<MfaSetupPanel showSummary={false} />
|
||||||
<SubscriptionPanel />
|
<SubscriptionPanel />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user