Skip to content

Commit 8bc54c9

Browse files
committed
refactor: enhance TwoFactor UI components with reusable Card layout, improve recovery codes visibility toggle, and standardize spacing
1 parent 1a24366 commit 8bc54c9

File tree

1 file changed

+42
-53
lines changed

1 file changed

+42
-53
lines changed

resources/js/pages/settings/TwoFactor.vue

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import HeadingSmall from '@/components/HeadingSmall.vue';
33
import InputError from '@/components/InputError.vue';
44
import { Badge } from '@/components/ui/badge';
55
import { Button } from '@/components/ui/button';
6+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
67
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
78
import { PinInput, PinInputGroup, PinInputSlot } from '@/components/ui/pin-input';
89
import { useClipboard } from '@/composables/useClipboard';
910
import { useTwoFactorAuth } from '@/composables/useTwoFactorAuth';
1011
import AppLayout from '@/layouts/AppLayout.vue';
1112
import SettingsLayout from '@/layouts/settings/Layout.vue';
1213
import { Form, Head } from '@inertiajs/vue3';
13-
import { Check, Copy, Eye, EyeOff, Loader2, LockKeyhole, ScanLine } from 'lucide-vue-next';
14+
import { Check, Copy, Eye, EyeOff, Loader2, LockKeyhole, RefreshCw, ScanLine, ShieldBan } from 'lucide-vue-next';
1415
import { computed, ComputedRef, nextTick, reactive, ref } from 'vue';
1516
1617
const props = withDefaults(
@@ -85,22 +86,22 @@ const modalConfig = computed(() => {
8586
return {
8687
title: 'You have enabled two factor authentication.',
8788
description: 'Two factor authentication is now enabled, scan the QR code or enter the setup key',
88-
buttonText: 'Close'
89+
buttonText: 'Close',
8990
};
9091
}
9192
9293
if (modalState.isInVerificationStep) {
9394
return {
9495
title: 'Verify Authentication Code',
9596
description: 'Enter the 6-digit code from your authenticator app',
96-
buttonText: 'Continue'
97+
buttonText: 'Continue',
9798
};
9899
}
99100
100101
return {
101102
title: 'Turn on 2-step Verification',
102103
description: 'To finish enabling two factor authentication, scan the QR code or enter the setup key',
103-
buttonText: 'Continue'
104+
buttonText: 'Continue',
104105
};
105106
});
106107
@@ -131,9 +132,9 @@ const handleSetupAction = (): void => {
131132
<SettingsLayout>
132133
<div class="space-y-6">
133134
<HeadingSmall title="Two-Factor Authentication" description="Manage your two-factor authentication settings" />
134-
<div v-if="!twoFactorEnabled" class="flex flex-col items-start justify-start space-y-5">
135+
<div v-if="!twoFactorEnabled" class="flex flex-col items-start justify-start space-y-4">
135136
<Badge variant="destructive"> Disabled </Badge>
136-
<p class="-translate-y-1 text-muted-foreground">
137+
<p class="text-muted-foreground">
137138
When you enable 2FA, you'll be prompted for a secure code during login, which can be retrieved from your phone's TOTP
138139
supported app.
139140
</p>
@@ -153,38 +154,27 @@ const handleSetupAction = (): void => {
153154
</Dialog>
154155
</div>
155156

156-
<div v-if="twoFactorEnabled" class="flex flex-col items-start justify-start space-y-5">
157+
<div v-if="twoFactorEnabled" class="flex flex-col items-start justify-start space-y-4">
157158
<Badge variant="default"> Enabled </Badge>
158159
<p class="text-muted-foreground">
159160
With two factor authentication enabled, you'll be prompted for a secure, random token during login, which you can retrieve
160161
from your TOTP Authenticator app.
161162
</p>
162163
<Dialog v-model:open="modalState.isOpen"></Dialog>
163-
<div>
164-
<div class="flex items-start rounded-t-xl border border-secondary p-4">
165-
<LockKeyhole class="mr-2 size-5 text-muted-foreground" />
166-
<div class="space-y-1">
167-
<h3 class="font-medium">2FA Recovery Codes</h3>
168-
<p class="text-sm text-muted-foreground">
169-
Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.
170-
</p>
171-
</div>
172-
</div>
173-
174-
<div class="rounded-b-xl border border-t-0 text-sm">
175-
<div
176-
@click="toggleRecoveryCodesVisibility"
177-
class="group flex h-10 cursor-pointer items-center justify-between px-5 text-xs select-none"
164+
<Card>
165+
<CardHeader>
166+
<CardTitle class="flex gap-3"><LockKeyhole class="size-4" /> 2FA Recovery Codes</CardTitle>
167+
<CardDescription
168+
>Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password
169+
manager.</CardDescription
178170
>
179-
<div :class="`relative ${!recoveryCodes.isVisible ? 'opacity-40 hover:opacity-60' : 'opacity-60'}`">
180-
<span v-if="!recoveryCodes.isVisible" class="flex items-center space-x-1">
181-
<Eye class="size-4" /> <span>View My Recovery Codes</span>
182-
</span>
183-
<span v-else class="flex items-center space-x-1">
184-
<EyeOff class="size-4" /> <span>Hide Recovery Codes</span>
185-
</span>
186-
</div>
187-
171+
</CardHeader>
172+
<CardContent>
173+
<div class="group flex items-center justify-between select-none">
174+
<Button @click="toggleRecoveryCodesVisibility">
175+
<component :is="recoveryCodes.isVisible ? EyeOff : Eye" class="size-4" />
176+
{{ recoveryCodes.isVisible ? 'Hide' : 'View' }} Recovery Codes
177+
</Button>
188178
<Form
189179
v-if="recoveryCodes.isVisible"
190180
:action="route('two-factor.recovery-codes')"
@@ -194,29 +184,31 @@ const handleSetupAction = (): void => {
194184
preserveScroll: true,
195185
}"
196186
>
197-
<Button size="sm" variant="secondary" type="submit" :disabled="processing" @click.stop="toggleRecoveryCodesVisibility">
187+
<Button variant="secondary" type="submit" :disabled="processing" @click.stop="toggleRecoveryCodesVisibility">
188+
<RefreshCw />
198189
{{ processing ? 'Regenerating...' : 'Regenerate Codes' }}
199190
</Button>
200191
</Form>
201192
</div>
202193

203194
<div
204-
class="relative overflow-hidden transition-all duration-300"
205-
:style="{
206-
height: recoveryCodes.isVisible ? 'auto' : '0',
207-
opacity: recoveryCodes.isVisible ? 1 : 0,
208-
}"
195+
:class="[
196+
'relative overflow-hidden transition-all duration-300',
197+
recoveryCodes.isVisible ? 'h-auto opacity-100' : 'h-0 opacity-0',
198+
]"
209199
>
210-
<div class="grid max-w-xl gap-1 bg-muted p-4 font-mono text-sm">
211-
<div v-for="(code, index) in recoveryCodes.list" :key="index">{{ code }}</div>
200+
<div class="space-y-3 mt-3">
201+
<div class="grid gap-1 rounded-lg bg-muted p-4 font-mono text-sm">
202+
<div v-for="(code, index) in recoveryCodes.list" :key="index">{{ code }}</div>
203+
</div>
204+
<p class="text-xs text-muted-foreground select-none">
205+
You have {{ recoveryCodes.list?.length }} recovery codes left. Each can be used once to access your account
206+
and will be removed after use. If you need more, click <span class="font-bold">Regenerate Codes</span> above.
207+
</p>
212208
</div>
213-
<p class="text-xs text-muted-foreground select-none">
214-
You have {{ recoveryCodes.list?.length }} recovery codes left. Each can be used once to access your account and
215-
will be removed after use. If you need more, click <span class="font-bold">Regenerate Codes</span> above.
216-
</p>
217209
</div>
218-
</div>
219-
</div>
210+
</CardContent>
211+
</Card>
220212

221213
<div class="relative inline">
222214
<Form
@@ -227,6 +219,7 @@ const handleSetupAction = (): void => {
227219
@success="disableTwoFactorAuthenticationSuccess"
228220
>
229221
<Button variant="destructive" type="submit" :disabled="processing">
222+
<ShieldBan />
230223
{{ processing ? 'Disabling...' : 'Disable 2FA' }}
231224
</Button>
232225
</Form>
@@ -238,15 +231,11 @@ const handleSetupAction = (): void => {
238231
<DialogHeader class="flex items-center justify-center">
239232
<div class="mb-3 w-auto rounded-full border border-border bg-card p-0.5 shadow-sm">
240233
<div class="relative overflow-hidden rounded-full border border-border bg-muted p-2.5">
241-
<div
242-
class="absolute inset-0 flex size-full items-stretch justify-around divide-x divide-border opacity-50 [&>div]:flex-1"
243-
>
244-
<div v-for="i in 5" :key="i"></div>
234+
<div class="absolute inset-0 grid grid-cols-5 opacity-50">
235+
<div v-for="i in 5" :key="`col-${i}`" class="border-r border-border last:border-r-0"></div>
245236
</div>
246-
<div
247-
class="absolute inset-0 flex size-full flex-col items-stretch justify-around divide-y divide-border opacity-50 [&>div]:flex-1"
248-
>
249-
<div v-for="i in 5" :key="i"></div>
237+
<div class="absolute inset-0 grid grid-rows-5 opacity-50">
238+
<div v-for="i in 5" :key="`row-${i}`" class="border-b border-border last:border-b-0"></div>
250239
</div>
251240
<ScanLine class="relative z-20 size-6 text-foreground" />
252241
</div>

0 commit comments

Comments
 (0)