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>
|
</VCardItem>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<!-- MFA method cards -->
|
<!-- MFA method cards -->
|
||||||
<VRow class="mb-4">
|
<VRow>
|
||||||
<!-- TOTP card -->
|
<!-- TOTP card -->
|
||||||
<VCol
|
<VCol
|
||||||
cols="12"
|
cols="12"
|
||||||
@@ -276,74 +276,71 @@ function copyRegeneratedCodes() {
|
|||||||
>
|
>
|
||||||
<VCard
|
<VCard
|
||||||
variant="outlined"
|
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
|
<VAvatar
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="40"
|
size="44"
|
||||||
rounded
|
rounded
|
||||||
class="me-3"
|
class="mb-3"
|
||||||
>
|
>
|
||||||
<VIcon
|
<VIcon
|
||||||
icon="tabler-device-mobile"
|
icon="tabler-device-mobile"
|
||||||
size="24"
|
size="26"
|
||||||
/>
|
/>
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
<div>
|
|
||||||
<h6 class="text-h6">
|
<h6 class="text-h6 mb-1">
|
||||||
Authenticator app
|
Authenticator app
|
||||||
</h6>
|
</h6>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||||
|
Gebruik een authenticator app zoals Google Authenticator of 1Password
|
||||||
|
</p>
|
||||||
|
|
||||||
<VChip
|
<VChip
|
||||||
:color="totpConfigured ? 'success' : 'default'"
|
:color="totpConfigured ? 'success' : 'default'"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-1"
|
:prepend-icon="totpConfigured ? 'tabler-check' : undefined"
|
||||||
>
|
>
|
||||||
{{ totpConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
{{ totpConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</VCardText>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<VCardActions class="px-4 pb-4 pt-0">
|
||||||
<template v-if="totpConfigured">
|
<template v-if="totpConfigured">
|
||||||
<VChip
|
<VChip
|
||||||
v-if="preferredMethod === 'totp'"
|
v-if="preferredMethod === 'totp'"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-1"
|
prepend-icon="tabler-check"
|
||||||
>
|
>
|
||||||
Primaire methode
|
Primaire methode
|
||||||
</VChip>
|
</VChip>
|
||||||
<VBtn
|
<VBtn
|
||||||
v-else
|
v-else
|
||||||
variant="text"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
|
||||||
class="mt-1 ms-n2"
|
|
||||||
:loading="setPreferredMethodMutation.isPending.value"
|
:loading="setPreferredMethodMutation.isPending.value"
|
||||||
@click="handleSetPreferred('totp')"
|
@click="handleSetPreferred('totp')"
|
||||||
>
|
>
|
||||||
Als primair instellen
|
Als primair instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</template>
|
|
||||||
<p
|
<VSpacer />
|
||||||
v-else
|
|
||||||
class="text-body-2 text-medium-emphasis mb-3"
|
|
||||||
>
|
|
||||||
Gebruik Google Authenticator, Authy of een andere app.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
v-if="totpConfigured"
|
variant="text"
|
||||||
variant="tonal"
|
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-2"
|
|
||||||
@click="showTotpSetup = true"
|
@click="showTotpSetup = true"
|
||||||
>
|
>
|
||||||
Opnieuw instellen
|
Opnieuw instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
v-else
|
v-else
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
@@ -352,6 +349,7 @@ function copyRegeneratedCodes() {
|
|||||||
>
|
>
|
||||||
Instellen
|
Instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
@@ -362,74 +360,71 @@ function copyRegeneratedCodes() {
|
|||||||
>
|
>
|
||||||
<VCard
|
<VCard
|
||||||
variant="outlined"
|
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
|
<VAvatar
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="40"
|
size="44"
|
||||||
rounded
|
rounded
|
||||||
class="me-3"
|
class="mb-3"
|
||||||
>
|
>
|
||||||
<VIcon
|
<VIcon
|
||||||
icon="tabler-mail"
|
icon="tabler-mail"
|
||||||
size="24"
|
size="26"
|
||||||
/>
|
/>
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
<div>
|
|
||||||
<h6 class="text-h6">
|
<h6 class="text-h6 mb-1">
|
||||||
E-mailcode
|
E-mailcode
|
||||||
</h6>
|
</h6>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||||
|
Ontvang een 6-cijferige code op {{ authStore.user?.email }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<VChip
|
<VChip
|
||||||
:color="emailConfigured ? 'success' : 'default'"
|
:color="emailConfigured ? 'success' : 'default'"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-1"
|
:prepend-icon="emailConfigured ? 'tabler-check' : undefined"
|
||||||
>
|
>
|
||||||
{{ emailConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
{{ emailConfigured ? 'Geconfigureerd' : 'Niet ingesteld' }}
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</VCardText>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<VCardActions class="px-4 pb-4 pt-0">
|
||||||
<template v-if="emailConfigured">
|
<template v-if="emailConfigured">
|
||||||
<VChip
|
<VChip
|
||||||
v-if="preferredMethod === 'email'"
|
v-if="preferredMethod === 'email'"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-1"
|
prepend-icon="tabler-check"
|
||||||
>
|
>
|
||||||
Primaire methode
|
Primaire methode
|
||||||
</VChip>
|
</VChip>
|
||||||
<VBtn
|
<VBtn
|
||||||
v-else
|
v-else
|
||||||
variant="text"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
|
||||||
class="mt-1 ms-n2"
|
|
||||||
:loading="setPreferredMethodMutation.isPending.value"
|
:loading="setPreferredMethodMutation.isPending.value"
|
||||||
@click="handleSetPreferred('email')"
|
@click="handleSetPreferred('email')"
|
||||||
>
|
>
|
||||||
Als primair instellen
|
Als primair instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</template>
|
|
||||||
<p
|
<VSpacer />
|
||||||
v-else
|
|
||||||
class="text-body-2 text-medium-emphasis mb-3"
|
|
||||||
>
|
|
||||||
Ontvang een code per e-mail bij elke login.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
v-if="emailConfigured"
|
variant="text"
|
||||||
variant="tonal"
|
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-2"
|
|
||||||
@click="showEmailSetup = true"
|
@click="showEmailSetup = true"
|
||||||
>
|
>
|
||||||
Opnieuw instellen
|
Opnieuw instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
v-else
|
v-else
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
@@ -438,63 +433,66 @@ function copyRegeneratedCodes() {
|
|||||||
>
|
>
|
||||||
Instellen
|
Instellen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
|
||||||
<!-- Backup codes status -->
|
<!-- Backup codes -->
|
||||||
<template v-if="isEnabled">
|
<VCard
|
||||||
<VDivider class="mb-4" />
|
v-if="isEnabled"
|
||||||
|
variant="outlined"
|
||||||
<div class="d-flex align-center justify-space-between mb-2">
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<div class="d-flex align-center justify-space-between">
|
||||||
<div>
|
<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>
|
<span class="text-body-1 font-weight-medium">Backup codes</span>
|
||||||
<p class="text-body-2 text-medium-emphasis mb-0">
|
</div>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">
|
||||||
{{ backupCodesRemaining }} van 10 resterend
|
{{ backupCodesRemaining }} van 10 resterend
|
||||||
</p>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<VBtn
|
<VBtn
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="small"
|
size="small"
|
||||||
|
prepend-icon="tabler-refresh"
|
||||||
@click="showRegenerateDialog = true; regeneratedCodes = []; regenerateCode = ''; regenerateError = ''"
|
@click="showRegenerateDialog = true; regeneratedCodes = []; regenerateCode = ''; regenerateError = ''"
|
||||||
>
|
>
|
||||||
Nieuwe codes genereren
|
Nieuwe codes
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VProgressLinear
|
<VProgressLinear
|
||||||
:model-value="backupCodesRemaining * 10"
|
:model-value="backupCodesRemaining * 10"
|
||||||
:color="backupCodesColor"
|
:color="backupCodesColor"
|
||||||
|
class="mt-3"
|
||||||
rounded
|
rounded
|
||||||
height="6"
|
height="6"
|
||||||
class="mb-2"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<!-- Card 2b: MFA disable (danger zone) -->
|
<!-- Disable MFA -->
|
||||||
<VCard
|
<div
|
||||||
v-if="isEnabled"
|
v-if="isEnabled"
|
||||||
class="mb-6 border-error"
|
class="mt-6"
|
||||||
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
|
<VBtn
|
||||||
|
variant="text"
|
||||||
color="error"
|
color="error"
|
||||||
variant="tonal"
|
size="small"
|
||||||
|
prepend-icon="tabler-shield-off"
|
||||||
@click="showDisableDialog = true"
|
@click="showDisableDialog = true"
|
||||||
>
|
>
|
||||||
Uitschakelen
|
Tweestapsverificatie uitschakelen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
@@ -682,3 +680,10 @@ function copyRegeneratedCodes() {
|
|||||||
--v-card-list-gap: 8px;
|
--v-card-list-gap: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mfa-method-card--primary {
|
||||||
|
border-color: rgb(var(--v-theme-primary));
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -676,17 +676,26 @@ function viewEvent(eventId: string) {
|
|||||||
<div class="d-flex flex-column gap-3 mb-4">
|
<div class="d-flex flex-column gap-3 mb-4">
|
||||||
<!-- TOTP row -->
|
<!-- TOTP row -->
|
||||||
<div class="d-flex align-center justify-space-between">
|
<div class="d-flex align-center justify-space-between">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center gap-2">
|
||||||
|
<VAvatar
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="32"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
<VIcon
|
<VIcon
|
||||||
icon="tabler-device-mobile"
|
icon="tabler-device-mobile"
|
||||||
size="20"
|
size="18"
|
||||||
class="me-2"
|
|
||||||
/>
|
/>
|
||||||
<span class="text-body-2 font-weight-medium me-2">Authenticator app</span>
|
</VAvatar>
|
||||||
|
<div>
|
||||||
|
<span class="text-body-2 font-weight-medium">Authenticator app</span>
|
||||||
|
<div class="d-flex align-center gap-1 mt-1">
|
||||||
<VChip
|
<VChip
|
||||||
:color="totpConfigured ? 'success' : 'default'"
|
:color="totpConfigured ? 'success' : 'default'"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
|
:prepend-icon="totpConfigured ? 'tabler-check' : undefined"
|
||||||
>
|
>
|
||||||
{{ totpConfigured ? 'Actief' : 'Niet ingesteld' }}
|
{{ totpConfigured ? 'Actief' : 'Niet ingesteld' }}
|
||||||
</VChip>
|
</VChip>
|
||||||
@@ -695,11 +704,12 @@ function viewEvent(eventId: string) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
class="ms-1"
|
|
||||||
>
|
>
|
||||||
Primair
|
Primair
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<VBtn
|
<VBtn
|
||||||
v-if="totpConfigured && preferredMethod !== 'totp'"
|
v-if="totpConfigured && preferredMethod !== 'totp'"
|
||||||
variant="text"
|
variant="text"
|
||||||
@@ -714,17 +724,26 @@ function viewEvent(eventId: string) {
|
|||||||
|
|
||||||
<!-- Email row -->
|
<!-- Email row -->
|
||||||
<div class="d-flex align-center justify-space-between">
|
<div class="d-flex align-center justify-space-between">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center gap-2">
|
||||||
|
<VAvatar
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="32"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
<VIcon
|
<VIcon
|
||||||
icon="tabler-mail"
|
icon="tabler-mail"
|
||||||
size="20"
|
size="18"
|
||||||
class="me-2"
|
|
||||||
/>
|
/>
|
||||||
<span class="text-body-2 font-weight-medium me-2">E-mailcode</span>
|
</VAvatar>
|
||||||
|
<div>
|
||||||
|
<span class="text-body-2 font-weight-medium">E-mailcode</span>
|
||||||
|
<div class="d-flex align-center gap-1 mt-1">
|
||||||
<VChip
|
<VChip
|
||||||
color="success"
|
color="success"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
|
prepend-icon="tabler-check"
|
||||||
>
|
>
|
||||||
Actief
|
Actief
|
||||||
</VChip>
|
</VChip>
|
||||||
@@ -733,11 +752,12 @@ function viewEvent(eventId: string) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
class="ms-1"
|
|
||||||
>
|
>
|
||||||
Primair
|
Primair
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<VBtn
|
<VBtn
|
||||||
v-if="preferredMethod !== 'email'"
|
v-if="preferredMethod !== 'email'"
|
||||||
variant="text"
|
variant="text"
|
||||||
@@ -752,9 +772,10 @@ function viewEvent(eventId: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
|
variant="text"
|
||||||
color="error"
|
color="error"
|
||||||
variant="tonal"
|
|
||||||
size="small"
|
size="small"
|
||||||
|
prepend-icon="tabler-shield-off"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
@click="showDisableDialog = true"
|
@click="showDisableDialog = true"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user