Initialer Laravel Commit für BetiX
This commit is contained in:
151
resources/js/pages/settings/TwoFactor.vue
Normal file
151
resources/js/pages/settings/TwoFactor.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, Head } from '@inertiajs/vue3';
|
||||
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
|
||||
import { useTwoFactorAuth } from '@/composables/useTwoFactorAuth';
|
||||
import { disable, enable } from '@/routes/two-factor';
|
||||
import UserLayout from '../../layouts/user/userlayout.vue';
|
||||
import TwoFactorRecoveryCodes from '@/components/TwoFactorRecoveryCodes.vue';
|
||||
import TwoFactorSetupModal from '@/components/TwoFactorSetupModal.vue';
|
||||
|
||||
type Props = {
|
||||
requiresConfirmation?: boolean;
|
||||
twoFactorEnabled?: boolean;
|
||||
};
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
requiresConfirmation: false,
|
||||
twoFactorEnabled: false,
|
||||
});
|
||||
|
||||
const { hasSetupData, clearTwoFactorAuthData } = useTwoFactorAuth();
|
||||
const showSetupModal = ref<boolean>(false);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => { if (window.lucide) window.lucide.createIcons(); });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTwoFactorAuthData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserLayout>
|
||||
<Head title="Two-Factor Authentication" />
|
||||
<section class="content">
|
||||
<div class="wrap">
|
||||
<div class="main-panel">
|
||||
<header class="page-head">
|
||||
<div class="head-flex">
|
||||
<div class="title-group">
|
||||
<div class="title">Two-Factor Authentication</div>
|
||||
<p class="subtitle">Manage your two-factor authentication settings</p>
|
||||
</div>
|
||||
<div class="security-badge">
|
||||
<i data-lucide="shield-check"></i>
|
||||
<span v-if="twoFactorEnabled">Enabled</span>
|
||||
<span v-else>Disabled</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="settings-nav">
|
||||
<a href="/settings/profile" class="nav-item"><i data-lucide="user"></i> Profile</a>
|
||||
<a href="/settings/kyc" class="nav-item"><i data-lucide="file-check"></i> KYC</a>
|
||||
<a href="/settings/security" class="nav-item"><i data-lucide="lock"></i> Security</a>
|
||||
<a href="/settings/two-factor" class="nav-item active"><i data-lucide="shield"></i> 2FA</a>
|
||||
</div>
|
||||
|
||||
<div class="tf-body">
|
||||
<div v-if="!twoFactorEnabled" class="section">
|
||||
<div class="badge bad">Disabled</div>
|
||||
|
||||
<p class="muted">
|
||||
When you enable two-factor authentication, you will be prompted for a secure pin during login.
|
||||
This pin can be retrieved from a TOTP-supported application on your phone.
|
||||
</p>
|
||||
|
||||
<div class="actions">
|
||||
<button v-if="hasSetupData" class="btn" @click="showSetupModal = true">
|
||||
<i data-lucide="shield-check"></i> Continue Setup
|
||||
</button>
|
||||
<Form v-else v-bind="enable.form()" @success="showSetupModal = true" #default="{ processing }">
|
||||
<button class="btn" type="submit" :disabled="processing">
|
||||
<i data-lucide="shield-check"></i> Enable 2FA
|
||||
</button>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="section">
|
||||
<div class="badge ok">Enabled</div>
|
||||
|
||||
<p class="muted">
|
||||
With two-factor authentication enabled, you will be prompted for a secure, random pin during login,
|
||||
which you can retrieve from the TOTP-supported application on your phone.
|
||||
</p>
|
||||
|
||||
<TwoFactorRecoveryCodes />
|
||||
|
||||
<div class="actions">
|
||||
<Form v-bind="disable.form()" #default="{ processing }">
|
||||
<button class="btn danger" type="submit" :disabled="processing">
|
||||
<i data-lucide="shield"></i> Disable 2FA
|
||||
</button>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TwoFactorSetupModal
|
||||
v-model:isOpen="showSetupModal"
|
||||
:requiresConfirmation="requiresConfirmation"
|
||||
:twoFactorEnabled="twoFactorEnabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</UserLayout>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
:global(:root) { --bg-card:#0a0a0a; --border:#151515; --cyan:#00f2ff; --magenta:#ff007a; --green:#00ff9d; }
|
||||
.content { padding: 30px; animation: fade-in 0.8s cubic-bezier(0.2, 0, 0, 1); }
|
||||
.wrap { max-width: 900px; margin: 0 auto; }
|
||||
.main-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.6); }
|
||||
.page-head { padding: 25px 30px; border-bottom: 1px solid var(--border); background: linear-gradient(to right, rgba(0,242,255,0.03), transparent); }
|
||||
.head-flex { display: flex; justify-content: space-between; align-items: center; }
|
||||
.title { font-size: 14px; font-weight: 900; color: #fff; letter-spacing: 3px; text-transform: uppercase; }
|
||||
.subtitle { color: #555; font-size: 12px; margin-top: 4px; font-weight: 600; }
|
||||
.security-badge { display:flex; align-items:center; gap:8px; color: var(--green); background: rgba(0,255,157,0.05); padding:6px 14px; border-radius:50px; border:1px solid rgba(0,255,157,0.1); font-size:10px; font-weight:900; text-transform:uppercase; letter-spacing:1px; }
|
||||
|
||||
/* Navigation */
|
||||
.settings-nav { display: flex; gap: 5px; padding: 15px 30px; border-bottom: 1px solid var(--border); background: rgba(0,0,0,0.2); overflow-x: auto; }
|
||||
.nav-item { display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 8px; font-size: 11px; font-weight: 800; color: #666; text-transform: uppercase; letter-spacing: 1px; transition: 0.2s; text-decoration: none; }
|
||||
.nav-item:hover { color: #fff; background: rgba(255,255,255,0.05); }
|
||||
.nav-item.active { color: var(--cyan); background: rgba(0,242,255,0.1); }
|
||||
.nav-item i { width: 14px; }
|
||||
|
||||
.tf-body { padding: 24px 28px; display: grid; gap: 22px; }
|
||||
.section { border: 1px solid var(--border); background: #0a0a0a; border-radius: 14px; padding: 18px; display: grid; gap: 12px; animation: slide-up 0.6s cubic-bezier(0.2, 0, 0, 1) backwards; }
|
||||
.badge { width: max-content; font-size:10px; font-weight:900; text-transform: uppercase; letter-spacing:1px; padding:6px 10px; border-radius:999px; border:1px solid #222; }
|
||||
.badge.ok { color:#000; background: var(--green); border-color: rgba(0,255,157,.35); }
|
||||
.badge.bad { color:#000; background: #ff5b5b; border-color: rgba(255,91,91,.35); }
|
||||
|
||||
.muted { color:#9aa0a6; }
|
||||
.actions { display:flex; gap:10px; align-items:center; }
|
||||
.btn { background: var(--cyan); color: #000; border: none; border-radius: 12px; padding: 12px 16px; font-size: 12px; font-weight: 900; text-transform: uppercase; letter-spacing: 1px; cursor: pointer; transition: 0.3s; box-shadow: 0 0 20px rgba(0,242,255,0.2); }
|
||||
.btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 6px 22px rgba(0,242,255,0.35); }
|
||||
.btn:disabled { opacity:.6; cursor:not-allowed; }
|
||||
.btn.danger { background:#ff5b5b; color:#000; box-shadow: 0 0 20px rgba(255,91,91,.25); }
|
||||
|
||||
@keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes slide-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wrap { max-width: 100%; }
|
||||
.tf-body { padding: 18px; }
|
||||
.settings-nav { padding: 10px 15px; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user