Skip to content

Commit 9b81898

Browse files
committed
wip
1 parent 2e55156 commit 9b81898

File tree

3 files changed

+48
-46
lines changed

3 files changed

+48
-46
lines changed

resources/js/composables/useClipboard.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface ClipboardOptions {
77
export function useClipboard(options: ClipboardOptions = {}) {
88
const { timeout = 1500 } = options;
99

10-
const copied = ref(false);
10+
const recentlyCopied = ref(false);
1111

1212
const copyToClipboard = async (text: string): Promise<void> => {
1313
if (typeof window === 'undefined' || !navigator.clipboard) {
@@ -16,15 +16,15 @@ export function useClipboard(options: ClipboardOptions = {}) {
1616

1717
try {
1818
await navigator.clipboard.writeText(text);
19-
copied.value = true;
20-
setTimeout(() => (copied.value = false), timeout);
19+
recentlyCopied.value = true;
20+
setTimeout(() => (recentlyCopied.value = false), timeout);
2121
} catch (error) {
2222
console.error('Failed to copy to clipboard:', error);
2323
}
2424
};
2525

2626
return {
27-
copied,
27+
recentlyCopied,
2828
copyToClipboard,
2929
};
3030
}

resources/js/pages/auth/TwoFactorChallenge.vue

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,25 @@ import AuthLayout from '@/layouts/AuthLayout.vue';
77
import { Form, Head } from '@inertiajs/vue3';
88
import { computed, ComputedRef, ref } from 'vue';
99
10-
const authConfig = computed(() => {
10+
interface AuthConfigContent {
11+
title: string;
12+
description: string;
13+
toggleText: string;
14+
}
15+
16+
const authConfigContent: ComputedRef<AuthConfigContent> = computed((): AuthConfigContent => {
17+
if (showRecoveryInput.value) {
18+
return {
19+
title: 'Recovery Code',
20+
description: 'Please confirm access to your account by entering one of your emergency recovery codes.',
21+
toggleText: 'login using an authentication code',
22+
};
23+
}
24+
1125
return {
12-
title: showRecoveryInput.value ? 'Recovery Code' : 'Authentication Code',
13-
description: showRecoveryInput.value
14-
? 'Please confirm access to your account by entering one of your emergency recovery codes.'
15-
: 'Enter the authentication code provided by your authenticator application.',
16-
toggleText: showRecoveryInput.value ? 'login using an authentication code' : 'login using a recovery code',
26+
title: 'Authentication Code',
27+
description: 'Enter the authentication code provided by your authenticator application.',
28+
toggleText: 'login using a recovery code',
1729
};
1830
});
1931
@@ -30,7 +42,7 @@ const codeValue: ComputedRef<string> = computed(() => code.value.join(''));
3042
</script>
3143

3244
<template>
33-
<AuthLayout :title="authConfig.title" :description="authConfig.description">
45+
<AuthLayout :title="authConfigContent.title" :description="authConfigContent.description">
3446
<Head title="Two Factor Authentication" />
3547

3648
<div class="space-y-6">
@@ -61,7 +73,7 @@ const codeValue: ComputedRef<string> = computed(() => code.value.join(''));
6173
class="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
6274
@click="() => toggleRecoveryMode(clearErrors)"
6375
>
64-
{{ authConfig.toggleText }}
76+
{{ authConfigContent.toggleText }}
6577
</button>
6678
</div>
6779
</Form>
@@ -80,7 +92,7 @@ const codeValue: ComputedRef<string> = computed(() => code.value.join(''));
8092
class="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
8193
@click="() => toggleRecoveryMode(clearErrors)"
8294
>
83-
{{ authConfig.toggleText }}
95+
{{ authConfigContent.toggleText }}
8496
</button>
8597
</div>
8698
</Form>

resources/js/pages/settings/TwoFactor.vue

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PinInput, PinInputGroup, PinInputSlot } from '@/components/ui/pin-input
99
import { useClipboard } from '@/composables/useClipboard';
1010
import AppLayout from '@/layouts/AppLayout.vue';
1111
import SettingsLayout from '@/layouts/settings/Layout.vue';
12+
import { BreadcrumbItem } from '@/types';
1213
import { Form, Head } from '@inertiajs/vue3';
1314
import { Check, Copy, Eye, EyeOff, Loader2, LockKeyhole, RefreshCw, ScanLine, ShieldBan, ShieldCheck } from 'lucide-vue-next';
1415
import { computed, nextTick, reactive, ref } from 'vue';
@@ -23,25 +24,33 @@ const props = withDefaults(defineProps<Props>(), {
2324
twoFactorEnabled: false,
2425
});
2526
26-
const breadcrumbs = [
27+
const breadcrumbs: BreadcrumbItem[] = [
2728
{
2829
title: 'Two-Factor Authentication',
2930
href: '/settings/two-factor',
3031
},
3132
];
3233
33-
const { copied, copyToClipboard } = useClipboard();
34+
const { recentlyCopied, copyToClipboard } = useClipboard();
3435
35-
const setupData = reactive({
36-
qrCodeSvg: null as string | null,
37-
manualSetupKey: null as string | null,
36+
const setupData: {
37+
qrCodeSvg: string | null;
38+
manualSetupKey: string | null;
39+
reset(): void;
40+
} = reactive({
41+
qrCodeSvg: null,
42+
manualSetupKey: null,
3843
reset() {
3944
this.qrCodeSvg = null;
4045
this.manualSetupKey = null;
4146
},
4247
});
4348
44-
const modalState = reactive({
49+
const modalState: {
50+
isOpen: boolean;
51+
isInVerificationStep: boolean;
52+
reset(): void;
53+
} = reactive({
4554
isOpen: false,
4655
isInVerificationStep: false,
4756
reset() {
@@ -99,7 +108,7 @@ const enableTwoFactorAuthenticationSuccess = async () => {
99108
const [qrResponse, keyResponse, codesResponse] = await Promise.all([
100109
fetch(route('two-factor.qr-code'), { headers: { Accept: 'application/json' } }),
101110
fetch(route('two-factor.secret-key'), { headers: { Accept: 'application/json' } }),
102-
fetch(route('two-factor.recovery-codes'), { headers: { Accept: 'application/json' } }),
111+
fetchRecoveryCodes(),
103112
]);
104113
105114
const { svg } = await qrResponse.json();
@@ -121,8 +130,12 @@ const disableTwoFactorAuthenticationSuccess = () => {
121130
code.value = [];
122131
};
123132
124-
const recoveryCodes = reactive({
125-
list: [] as string[],
133+
const recoveryCodes: {
134+
list: string[];
135+
isVisible: boolean;
136+
reset(): void;
137+
} = reactive({
138+
list: [],
126139
isVisible: false,
127140
reset() {
128141
this.list = [];
@@ -293,11 +306,6 @@ const verificationCode = computed(() => code.value.join(''));
293306
</div>
294307
<div v-else class="relative z-10 overflow-hidden border p-5">
295308
<div v-html="setupData.qrCodeSvg" class="flex aspect-square size-full items-center justify-center" />
296-
<div v-if="setupData.qrCodeSvg" class="animate-scanning-line absolute inset-0 h-full w-full">
297-
<div
298-
class="absolute inset-x-0 h-0.5 bg-blue-500 opacity-60 transition-all duration-300 ease-in-out"
299-
/>
300-
</div>
301309
</div>
302310
</div>
303311
</div>
@@ -329,7 +337,7 @@ const verificationCode = computed(() => code.value.join(''));
329337
@click="copyToClipboard(setupData.manualSetupKey || '')"
330338
class="relative block h-auto border-l border-border px-3 hover:bg-muted"
331339
>
332-
<Check v-if="copied" class="w-4 text-green-500" />
340+
<Check v-if="recentlyCopied" class="w-4 text-green-500" />
333341
<Copy v-else class="w-4" />
334342
</button>
335343
</template>
@@ -391,21 +399,3 @@ const verificationCode = computed(() => code.value.join(''));
391399
</SettingsLayout>
392400
</AppLayout>
393401
</template>
394-
395-
<style scoped>
396-
@keyframes scan {
397-
0% {
398-
top: 0;
399-
}
400-
50% {
401-
top: 100%;
402-
}
403-
100% {
404-
top: 0;
405-
}
406-
}
407-
408-
.animate-scanning-line div {
409-
animation: scan 3s ease-in-out infinite;
410-
}
411-
</style>

0 commit comments

Comments
 (0)