Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12, Vue 3, Inertia.js 2, and Tailwind CSS 4. Story 0.1 (rename folders to declarations in database) is implemented and code-reviewed: migration, rollback, and 6 Pest tests all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
5.1 KiB
Vue
134 lines
5.1 KiB
Vue
<script setup lang="ts">
|
|
import { Form, Head } from '@inertiajs/vue3';
|
|
import { computed, ref } from 'vue';
|
|
import InputError from '@/components/InputError.vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import {
|
|
InputOTP,
|
|
InputOTPGroup,
|
|
InputOTPSlot,
|
|
} from '@/components/ui/input-otp';
|
|
import AuthLayout from '@/layouts/AuthLayout.vue';
|
|
import type { TwoFactorConfigContent } from '@/types';
|
|
import { store } from '@/routes/two-factor/login';
|
|
|
|
const authConfigContent = computed<TwoFactorConfigContent>(() => {
|
|
if (showRecoveryInput.value) {
|
|
return {
|
|
title: 'Recovery Code',
|
|
description:
|
|
'Please confirm access to your account by entering one of your emergency recovery codes.',
|
|
buttonText: 'login using an authentication code',
|
|
};
|
|
}
|
|
|
|
return {
|
|
title: 'Authentication Code',
|
|
description:
|
|
'Enter the authentication code provided by your authenticator application.',
|
|
buttonText: 'login using a recovery code',
|
|
};
|
|
});
|
|
|
|
const showRecoveryInput = ref<boolean>(false);
|
|
|
|
const toggleRecoveryMode = (clearErrors: () => void): void => {
|
|
showRecoveryInput.value = !showRecoveryInput.value;
|
|
clearErrors();
|
|
code.value = '';
|
|
};
|
|
|
|
const code = ref<string>('');
|
|
</script>
|
|
|
|
<template>
|
|
<AuthLayout
|
|
:title="authConfigContent.title"
|
|
:description="authConfigContent.description"
|
|
>
|
|
<Head title="Two-Factor Authentication" />
|
|
|
|
<div class="space-y-6">
|
|
<template v-if="!showRecoveryInput">
|
|
<Form
|
|
v-bind="store.form()"
|
|
class="space-y-4"
|
|
reset-on-error
|
|
@error="code = ''"
|
|
#default="{ errors, processing, clearErrors }"
|
|
>
|
|
<input type="hidden" name="code" :value="code" />
|
|
<div
|
|
class="flex flex-col items-center justify-center space-y-3 text-center"
|
|
>
|
|
<div class="flex w-full items-center justify-center">
|
|
<InputOTP
|
|
id="otp"
|
|
v-model="code"
|
|
:maxlength="6"
|
|
:disabled="processing"
|
|
autofocus
|
|
>
|
|
<InputOTPGroup>
|
|
<InputOTPSlot
|
|
v-for="index in 6"
|
|
:key="index"
|
|
:index="index - 1"
|
|
/>
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</div>
|
|
<InputError :message="errors.code" />
|
|
</div>
|
|
<Button type="submit" class="w-full" :disabled="processing"
|
|
>Continue</Button
|
|
>
|
|
<div class="text-center text-sm text-muted-foreground">
|
|
<span>or you can </span>
|
|
<button
|
|
type="button"
|
|
class="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
|
|
@click="() => toggleRecoveryMode(clearErrors)"
|
|
>
|
|
{{ authConfigContent.buttonText }}
|
|
</button>
|
|
</div>
|
|
</Form>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<Form
|
|
v-bind="store.form()"
|
|
class="space-y-4"
|
|
reset-on-error
|
|
#default="{ errors, processing, clearErrors }"
|
|
>
|
|
<Input
|
|
name="recovery_code"
|
|
type="text"
|
|
placeholder="Enter recovery code"
|
|
:autofocus="showRecoveryInput"
|
|
required
|
|
/>
|
|
<InputError :message="errors.recovery_code" />
|
|
<Button type="submit" class="w-full" :disabled="processing"
|
|
>Continue</Button
|
|
>
|
|
|
|
<div class="text-center text-sm text-muted-foreground">
|
|
<span>or you can </span>
|
|
<button
|
|
type="button"
|
|
class="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
|
|
@click="() => toggleRecoveryMode(clearErrors)"
|
|
>
|
|
{{ authConfigContent.buttonText }}
|
|
</button>
|
|
</div>
|
|
</Form>
|
|
</template>
|
|
</div>
|
|
</AuthLayout>
|
|
</template>
|