Skip to content

Commit 2ef7b76

Browse files
authored
Merge pull request #26 from devforth/feature/AdminForth/1565/improve-2fa-modals-for-passkey
feat: enhance TwoFactorsConfirmation modal with improved layout and u…
2 parents 500becc + 3efb1e7 commit 2ef7b76

1 file changed

Lines changed: 96 additions & 65 deletions

File tree

custom/TwoFactorsConfirmation.vue

Lines changed: 96 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,73 +7,83 @@
77
'background-blend-mode': coreStore.config?.removeBackgroundBlendMode ? 'normal' : 'darken'
88
}: {}"
99
>
10-
11-
<div v-if="isLoading===false" id="authentication-modal" tabindex="-1" class="af-two-factors-confirmation overflow-y-auto overflow-x-hidden z-50 min-w-[00px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
12-
<div class="relative p-4 w-full max-w-md max-h-full">
13-
<!-- Modal content -->
14-
<div class="af-login-popup relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black text-gray-500" :class="codeError ? 'rounded-b-none' : ''">
15-
<div class="p-8 w-full max-w-md max-h-full custom-auth-wrapper" >
16-
<div v-if="confirmationMode === 'code'" class="af-totp-confirmation">
17-
<div id="mfaCode-label" class="mx-4">{{$t('Please enter your authenticator code')}} </div>
18-
<div class="mt-4 w-full flex flex-col gap-4 justify-center" ref="otpRoot">
19-
<v-otp-input
20-
ref="code"
21-
container-class="grid grid-cols-6 gap-3 w-full"
22-
input-classes="bg-gray-50 text-center justify-center otp-input border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-10 h-10 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
23-
:num-inputs="6"
24-
inputType="number"
25-
inputmode="numeric"
26-
:should-auto-focus="true"
27-
:should-focus-order="true"
28-
v-model:value="bindValue"
29-
@on-complete="handleOnComplete"
30-
/>
31-
<div class="flex items-center justify-between w-full">
32-
<Link v-if="confirmationMode === 'code' && doesUserHavePasskeys" :to="{ hash: '#passkey' }" class="w-max underline hover:no-underline hover:cursor-pointer text-lightPrimary whitespace-nowrap">{{$t('Use passkey')}}</Link>
33-
<Link
34-
v-if="confirmationMode === 'code'"
35-
to="/login"
36-
class="w-max"
37-
>
38-
{{$t('Back to login')}}
39-
</Link>
40-
</div>
41-
</div>
42-
</div>
43-
<div v-else class="af-passkey-confirmation flex flex-col items-center justify-center py-4 gap-6">
44-
<IconShieldOutline class="w-16 h-16 text-lightPrimary dark:text-darkPrimary"/>
45-
<p class="text-4xl font-semibold mb-4">{{$t('Passkey')}}</p>
46-
<p class="mb-2 max-w-[300px]">{{$t('When you are ready, authenticate using the button below')}}</p>
47-
<Button @click="usePasskeyButton" :disabled="isFetchingPasskey" :loader="isFetchingPasskey" class="w-full mx-16">
48-
{{$t('Use passkey')}}
49-
</Button>
50-
<div v-if="confirmationMode === 'passkey'" class="max-w-sm px-6 pt-3 w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
51-
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400">
52-
{{$t('Have issues with passkey?')}}
53-
<div v-if="doesUserHavePasskeys" class="flex justify-start cursor-pointer gap-2" >
54-
<Link v-if="confirmationMode === 'passkey'" :to="{ hash: '#code' }" class="underline hover:no-underline text-lightPrimary whitespace-nowrap">{{$t('use TOTP')}}</Link>
55-
<p> {{$t('or')}}</p>
56-
<Link
57-
to="/login"
58-
class="w-full"
59-
>
60-
{{$t('back to login')}}
61-
</Link>
62-
</div>
63-
</p>
64-
</div>
65-
</div>
10+
<div v-if="isLoading===false" id="authentication-modal" class="af-two-factors-confirmation flex items-center justify-center w-full p-4">
11+
<div class="relative w-full max-w-md">
12+
13+
<div class="af-login-popup relative bg-white dark:bg-gray-700 rounded-lg shadow p-6" :class="codeError ? 'rounded-b-none' : ''">
14+
15+
<div class="af-2fa-header flex flex-col items-center justify-center gap-3 mb-6">
16+
<div class="af-2fa-icon-wrap w-14 h-14 shrink-0 flex items-center justify-center rounded-full bg-lightPrimary dark:bg-darkPrimary">
17+
<IconShieldOutline class="af-2fa-shield-icon w-7 h-7 text-white" />
18+
</div>
19+
<div class="af-2fa-title-wrap text-center">
20+
<p class="text-xl font-medium text-gray-900 dark:text-white">
21+
{{ confirmationMode === 'code' ? $t('Two-factor Auth') : $t('Passkey') }}
22+
</p>
23+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
24+
{{ confirmationMode === 'code'
25+
? $t('Please enter your authenticator code')
26+
: $t('When you are ready, authenticate using the button below')
27+
}}
28+
</p>
29+
</div>
30+
</div>
31+
32+
<div v-if="confirmationMode === 'code'" class="af-2fa-otp-root flex flex-col items-center gap-6" ref="otpRoot">
33+
<v-otp-input
34+
ref="code"
35+
container-class="grid grid-cols-6 gap-3"
36+
input-classes="bg-gray-50 text-center flex justify-center otp-input border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-10 h-10 dark:bg-gray-600 dark:border-gray-500 dark:text-white"
37+
:num-inputs="6"
38+
inputType="number"
39+
inputmode="numeric"
40+
:should-auto-focus="true"
41+
v-model:value="bindValue"
42+
@on-complete="handleOnComplete"
43+
/>
44+
45+
<div class="af-2fa-footer text-center text-xs text-gray-500 dark:text-gray-400">
46+
<p>
47+
{{$t('Having trouble?')}}
48+
<button v-if="doesUserHavePasskeys" type="button"
49+
class="hover-link bg-transparent text-[#16537E] border-none p-0 cursor-pointer ml-1"
50+
@click="router.push({ hash: '#passkey' })">
51+
{{$t('Use passkey instead')}}
52+
</button>
53+
<span v-if="doesUserHavePasskeys" class="mx-1">{{$t('or')}}</span>
54+
<Link to="/login" class="hover-link">
55+
{{$t('Back to login')}}
56+
</Link>
57+
</p>
6658
</div>
59+
</div>
60+
61+
<div v-else class="af-2fa-passkey-root flex flex-col gap-6">
62+
<Button @click="usePasskeyButton" :disabled="isFetchingPasskey" :loader="isFetchingPasskey" class="af-2fa-passkey-btn w-full flex items-center justify-center gap-2">
63+
<IconShieldOutline class="w-4 h-4" />
64+
{{$t('Use passkey to verify')}}
65+
</Button>
66+
67+
<div class="af-2fa-footer text-center text-xs text-gray-500 dark:text-gray-400">
68+
<p>
69+
{{$t('Having trouble?')}}
70+
<button type="button"
71+
class="hover-link bg-transparent text-[#16537E] border-none p-0 cursor-pointer ml-1"
72+
@click="router.push({ hash: '#code' })">
73+
{{$t('Use TOTP instead')}}
74+
</button>
75+
<span class="mx-1">{{$t('or')}}</span>
76+
<Link to="/login" class="hover-link">
77+
{{$t('Back to login')}}
78+
</Link>
79+
</p>
80+
</div>
81+
</div>
6782
</div>
68-
<div
69-
v-if="codeError"
70-
class="af-two-factors-confirmation-error relative top-full left-0 bg-red-100 text-red-700 text-sm px-2 py-2 rounded-b-lg shadow"
71-
>
72-
<p class="pl-6">{{ codeError }} </p>
83+
84+
<div v-if="codeError" class="af-two-factors-confirmation-error relative bg-red-100 text-red-700 text-xs px-4 py-2 rounded-b-lg shadow border-t border-red-200 text-center">
85+
{{ codeError }}
7386
</div>
74-
<div v-else class="h-[36px] opacity-0">
75-
76-
</div>
7787
</div>
7888
</div>
7989
<div v-else>
@@ -477,5 +487,26 @@
477487
@apply ml-4;
478488
}
479489
}
480-
}
490+
}
491+
492+
:deep(.otp-input-container) {
493+
display: flex;
494+
gap: 0.75rem;
495+
}
496+
497+
.hover-link {
498+
text-decoration: none !important;
499+
display: inline-block;
500+
width: fit-content;
501+
margin: 0 auto;
502+
503+
&:hover {
504+
text-decoration: underline !important;
505+
}
506+
}
507+
508+
button.hover-link {
509+
font-family: inherit;
510+
font-size: inherit;
511+
}
481512
</style>

0 commit comments

Comments
 (0)