refactor: improve MFA section visual hierarchy in SecurityTab
Redesigns the MFA method cards and supporting sections for better visual hierarchy and professional styling: Method cards (organizer): - Vertical layout with large icon (VAvatar 44px) at top - Description text explaining each method - Status chip with check icon when configured - VCardActions with primary chip/button + "Opnieuw instellen" - Primary method card highlighted with 2px primary border - Proper h-100 for equal height side-by-side Backup codes: - Separate outlined VCard with key icon, progress bar, refresh button - Cleaner spacing and visual grouping Disable MFA: - Replaced heavy danger-zone card with subtle text button (tabler-shield-off icon, error color) — less visual weight for a rarely-used destructive action Portal: - Per-method rows with VAvatar icons and stacked status chips - Matching text-button style for disable action Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -268,7 +268,7 @@ function copyRegeneratedCodes() {
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<!-- MFA method cards -->
|
||||
<VRow class="mb-4">
|
||||
<VRow>
|
||||
<!-- TOTP card -->
|
||||
<VCol
|
||||
cols="12"
|
||||
@@ -276,82 +276,80 @@ function copyRegeneratedCodes() {
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-4 h-100"
|
||||
:class="['mfa-method-card h-100', { 'mfa-method-card--primary': totpConfigured && preferredMethod === 'totp' }]"
|
||||
>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VCardText>
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="40"
|
||||
size="44"
|
||||
rounded
|
||||
class="me-3"
|
||||
class="mb-3"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-device-mobile"
|
||||
size="24"
|
||||
size="26"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<h6 class="text-h6">
|
||||
Authenticator app
|
||||
</h6>
|
||||
<VChip
|
||||
:color="totpConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
>
|
||||
{{ totpConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="totpConfigured">
|
||||
<h6 class="text-h6 mb-1">
|
||||
Authenticator app
|
||||
</h6>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
Gebruik een authenticator app zoals Google Authenticator of 1Password
|
||||
</p>
|
||||
|
||||
<VChip
|
||||
v-if="preferredMethod === 'totp'"
|
||||
color="primary"
|
||||
:color="totpConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
:prepend-icon="totpConfigured ? 'tabler-check' : undefined"
|
||||
>
|
||||
Primaire methode
|
||||
{{ totpConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
</VCardText>
|
||||
|
||||
<VCardActions class="px-4 pb-4 pt-0">
|
||||
<template v-if="totpConfigured">
|
||||
<VChip
|
||||
v-if="preferredMethod === 'totp'"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-check"
|
||||
>
|
||||
Primaire methode
|
||||
</VChip>
|
||||
<VBtn
|
||||
v-else
|
||||
variant="tonal"
|
||||
size="small"
|
||||
:loading="setPreferredMethodMutation.isPending.value"
|
||||
@click="handleSetPreferred('totp')"
|
||||
>
|
||||
Als primair instellen
|
||||
</VBtn>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<VBtn
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="showTotpSetup = true"
|
||||
>
|
||||
Opnieuw instellen
|
||||
</VBtn>
|
||||
</template>
|
||||
|
||||
<VBtn
|
||||
v-else
|
||||
variant="text"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
color="primary"
|
||||
class="mt-1 ms-n2"
|
||||
:loading="setPreferredMethodMutation.isPending.value"
|
||||
@click="handleSetPreferred('totp')"
|
||||
@click="showTotpSetup = true"
|
||||
>
|
||||
Als primair instellen
|
||||
Instellen
|
||||
</VBtn>
|
||||
</template>
|
||||
<p
|
||||
v-else
|
||||
class="text-body-2 text-medium-emphasis mb-3"
|
||||
>
|
||||
Gebruik Google Authenticator, Authy of een andere app.
|
||||
</p>
|
||||
|
||||
<VBtn
|
||||
v-if="totpConfigured"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-2"
|
||||
@click="showTotpSetup = true"
|
||||
>
|
||||
Opnieuw instellen
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-else
|
||||
variant="tonal"
|
||||
size="small"
|
||||
@click="showTotpSetup = true"
|
||||
>
|
||||
Instellen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
@@ -362,139 +360,139 @@ function copyRegeneratedCodes() {
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-4 h-100"
|
||||
:class="['mfa-method-card h-100', { 'mfa-method-card--primary': emailConfigured && preferredMethod === 'email' }]"
|
||||
>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VCardText>
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="40"
|
||||
size="44"
|
||||
rounded
|
||||
class="me-3"
|
||||
class="mb-3"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-mail"
|
||||
size="24"
|
||||
size="26"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<h6 class="text-h6">
|
||||
E-mailcode
|
||||
</h6>
|
||||
<VChip
|
||||
:color="emailConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
>
|
||||
{{ emailConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="emailConfigured">
|
||||
<h6 class="text-h6 mb-1">
|
||||
E-mailcode
|
||||
</h6>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
Ontvang een 6-cijferige code op {{ authStore.user?.email }}
|
||||
</p>
|
||||
|
||||
<VChip
|
||||
v-if="preferredMethod === 'email'"
|
||||
color="primary"
|
||||
:color="emailConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
:prepend-icon="emailConfigured ? 'tabler-check' : undefined"
|
||||
>
|
||||
Primaire methode
|
||||
{{ emailConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
</VCardText>
|
||||
|
||||
<VCardActions class="px-4 pb-4 pt-0">
|
||||
<template v-if="emailConfigured">
|
||||
<VChip
|
||||
v-if="preferredMethod === 'email'"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-check"
|
||||
>
|
||||
Primaire methode
|
||||
</VChip>
|
||||
<VBtn
|
||||
v-else
|
||||
variant="tonal"
|
||||
size="small"
|
||||
:loading="setPreferredMethodMutation.isPending.value"
|
||||
@click="handleSetPreferred('email')"
|
||||
>
|
||||
Als primair instellen
|
||||
</VBtn>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<VBtn
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="showEmailSetup = true"
|
||||
>
|
||||
Opnieuw instellen
|
||||
</VBtn>
|
||||
</template>
|
||||
|
||||
<VBtn
|
||||
v-else
|
||||
variant="text"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
color="primary"
|
||||
class="mt-1 ms-n2"
|
||||
:loading="setPreferredMethodMutation.isPending.value"
|
||||
@click="handleSetPreferred('email')"
|
||||
@click="showEmailSetup = true"
|
||||
>
|
||||
Als primair instellen
|
||||
Instellen
|
||||
</VBtn>
|
||||
</template>
|
||||
<p
|
||||
v-else
|
||||
class="text-body-2 text-medium-emphasis mb-3"
|
||||
>
|
||||
Ontvang een code per e-mail bij elke login.
|
||||
</p>
|
||||
|
||||
<VBtn
|
||||
v-if="emailConfigured"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
class="mt-2"
|
||||
@click="showEmailSetup = true"
|
||||
>
|
||||
Opnieuw instellen
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-else
|
||||
variant="tonal"
|
||||
size="small"
|
||||
@click="showEmailSetup = true"
|
||||
>
|
||||
Instellen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- Backup codes status -->
|
||||
<template v-if="isEnabled">
|
||||
<VDivider class="mb-4" />
|
||||
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<div>
|
||||
<span class="text-body-1 font-weight-medium">Backup codes</span>
|
||||
<p class="text-body-2 text-medium-emphasis mb-0">
|
||||
{{ backupCodesRemaining }} van 10 resterend
|
||||
</p>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
size="small"
|
||||
@click="showRegenerateDialog = true; regeneratedCodes = []; regenerateCode = ''; regenerateError = ''"
|
||||
>
|
||||
Nieuwe codes genereren
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<VProgressLinear
|
||||
:model-value="backupCodesRemaining * 10"
|
||||
:color="backupCodesColor"
|
||||
rounded
|
||||
height="6"
|
||||
class="mb-2"
|
||||
/>
|
||||
</template>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Card 2b: MFA disable (danger zone) -->
|
||||
<VCard
|
||||
v-if="isEnabled"
|
||||
class="mb-6 border-error"
|
||||
variant="outlined"
|
||||
>
|
||||
<VCardText class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<h6 class="text-h6 text-error mb-1">
|
||||
Tweestapsverificatie uitschakelen
|
||||
</h6>
|
||||
<p class="text-body-2 text-medium-emphasis mb-0">
|
||||
Dit vermindert de beveiliging van je account.
|
||||
</p>
|
||||
</div>
|
||||
<VBtn
|
||||
color="error"
|
||||
variant="tonal"
|
||||
@click="showDisableDialog = true"
|
||||
<!-- Backup codes -->
|
||||
<VCard
|
||||
v-if="isEnabled"
|
||||
variant="outlined"
|
||||
class="mt-4"
|
||||
>
|
||||
Uitschakelen
|
||||
</VBtn>
|
||||
<VCardText>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="d-flex align-center gap-2 mb-1">
|
||||
<VIcon
|
||||
icon="tabler-key"
|
||||
size="20"
|
||||
class="text-medium-emphasis"
|
||||
/>
|
||||
<span class="text-body-1 font-weight-medium">Backup codes</span>
|
||||
</div>
|
||||
<span class="text-body-2 text-medium-emphasis">
|
||||
{{ backupCodesRemaining }} van 10 resterend
|
||||
</span>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-refresh"
|
||||
@click="showRegenerateDialog = true; regeneratedCodes = []; regenerateCode = ''; regenerateError = ''"
|
||||
>
|
||||
Nieuwe codes
|
||||
</VBtn>
|
||||
</div>
|
||||
<VProgressLinear
|
||||
:model-value="backupCodesRemaining * 10"
|
||||
:color="backupCodesColor"
|
||||
class="mt-3"
|
||||
rounded
|
||||
height="6"
|
||||
/>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Disable MFA -->
|
||||
<div
|
||||
v-if="isEnabled"
|
||||
class="mt-6"
|
||||
>
|
||||
<VBtn
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
prepend-icon="tabler-shield-off"
|
||||
@click="showDisableDialog = true"
|
||||
>
|
||||
Tweestapsverificatie uitschakelen
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
@@ -682,3 +680,10 @@ function copyRegeneratedCodes() {
|
||||
--v-card-list-gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mfa-method-card--primary {
|
||||
border-color: rgb(var(--v-theme-primary));
|
||||
border-width: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -676,29 +676,39 @@ function viewEvent(eventId: string) {
|
||||
<div class="d-flex flex-column gap-3 mb-4">
|
||||
<!-- TOTP row -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
icon="tabler-device-mobile"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
<span class="text-body-2 font-weight-medium me-2">Authenticator app</span>
|
||||
<VChip
|
||||
:color="totpConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
>
|
||||
{{ totpConfigured ? 'Actief' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="totpConfigured && preferredMethod === 'totp'"
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
class="ms-1"
|
||||
size="32"
|
||||
rounded
|
||||
>
|
||||
Primair
|
||||
</VChip>
|
||||
<VIcon
|
||||
icon="tabler-device-mobile"
|
||||
size="18"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<span class="text-body-2 font-weight-medium">Authenticator app</span>
|
||||
<div class="d-flex align-center gap-1 mt-1">
|
||||
<VChip
|
||||
:color="totpConfigured ? 'success' : 'default'"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
:prepend-icon="totpConfigured ? 'tabler-check' : undefined"
|
||||
>
|
||||
{{ totpConfigured ? 'Actief' : 'Niet ingesteld' }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="totpConfigured && preferredMethod === 'totp'"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
>
|
||||
Primair
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VBtn
|
||||
v-if="totpConfigured && preferredMethod !== 'totp'"
|
||||
@@ -714,29 +724,39 @@ function viewEvent(eventId: string) {
|
||||
|
||||
<!-- Email row -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
icon="tabler-mail"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
<span class="text-body-2 font-weight-medium me-2">E-mailcode</span>
|
||||
<VChip
|
||||
color="success"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
>
|
||||
Actief
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="preferredMethod === 'email'"
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
class="ms-1"
|
||||
size="32"
|
||||
rounded
|
||||
>
|
||||
Primair
|
||||
</VChip>
|
||||
<VIcon
|
||||
icon="tabler-mail"
|
||||
size="18"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<span class="text-body-2 font-weight-medium">E-mailcode</span>
|
||||
<div class="d-flex align-center gap-1 mt-1">
|
||||
<VChip
|
||||
color="success"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
prepend-icon="tabler-check"
|
||||
>
|
||||
Actief
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="preferredMethod === 'email'"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="x-small"
|
||||
>
|
||||
Primair
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VBtn
|
||||
v-if="preferredMethod !== 'email'"
|
||||
@@ -752,9 +772,10 @@ function viewEvent(eventId: string) {
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
variant="text"
|
||||
color="error"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-shield-off"
|
||||
class="mb-4"
|
||||
@click="showDisableDialog = true"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user