wraith/src/layouts/UnlockLayout.vue
Vantz Stockwell d4bfb3d5fd
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
refactor: migrate UnlockLayout to Tailwind + extract ToolShell wrapper
- Convert all ~40 inline styles in UnlockLayout.vue to Tailwind CSS v4 arbitrary-value classes, matching MainLayout.vue color conventions (CSS variables + hex arbitraries). Visual appearance preserved exactly.
- Create ToolShell.vue reusable wrapper that owns output/running state and execute/setOutput API via defineExpose.
- Refactor PingTool, TracerouteTool, DnsLookup, WhoisTool, BandwidthTest to use ToolShell — each tool now contains only its unique inputs and invoke calls. Zero vue-tsc errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:53:57 -04:00

144 lines
5.0 KiB
Vue

<script setup lang="ts">
import { ref, computed } from "vue";
import { useAppStore } from "@/stores/app.store";
const app = useAppStore();
const password = ref("");
const confirmPassword = ref("");
const localError = ref<string | null>(null);
const loading = ref(false);
const isFirstRun = computed(() => app.isFirstRun);
async function handleSubmit() {
localError.value = null;
if (!password.value.trim()) {
localError.value = "Password is required.";
return;
}
if (isFirstRun.value) {
if (password.value.length < 12) {
localError.value = "Master password must be at least 12 characters.";
return;
}
if (password.value !== confirmPassword.value) {
localError.value = "Passwords do not match.";
return;
}
}
loading.value = true;
try {
if (isFirstRun.value) {
await app.createVault(password.value);
} else {
await app.unlock(password.value);
}
} catch {
// app.error is already set by the store; surface it locally so the template
// only needs to check one place.
localError.value = app.error ?? "An unexpected error occurred.";
} finally {
loading.value = false;
}
}
const displayError = computed(() => localError.value ?? app.error);
</script>
<template>
<div class="h-full flex items-center justify-center bg-[var(--wraith-bg-primary)]">
<div class="w-full max-w-[400px] p-10 bg-[var(--wraith-bg-secondary)] border border-[var(--wraith-border)] rounded-xl shadow-[0_8px_32px_rgba(0,0,0,0.5)]">
<!-- Logo -->
<div class="text-center mb-8">
<span class="text-[2rem] font-extrabold tracking-[0.3em] text-[var(--wraith-accent-blue)] uppercase font-['Inter',monospace]">
WRAITH
</span>
<p class="mt-2 text-[0.8rem] text-[var(--wraith-text-muted)] tracking-[0.15em] uppercase">
{{ isFirstRun ? "Initialize Secure Vault" : "Secure Desktop" }}
</p>
</div>
<!-- Form -->
<form @submit.prevent="handleSubmit" class="flex flex-col gap-4">
<!-- Master password -->
<div>
<label
for="master-password"
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
>
MASTER PASSWORD
</label>
<input
id="master-password"
v-model="password"
type="password"
autocomplete="current-password"
placeholder="Enter master password"
:disabled="loading"
class="w-full px-[0.9rem] py-[0.65rem] bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] rounded-[6px] text-[var(--wraith-text-primary)] text-[0.95rem] outline-none transition-colors duration-150 box-border focus:border-[var(--wraith-accent-blue)]"
/>
</div>
<!-- Confirm password — only shown on first run -->
<div v-if="isFirstRun">
<label
for="confirm-password"
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
>
CONFIRM PASSWORD
</label>
<input
id="confirm-password"
v-model="confirmPassword"
type="password"
autocomplete="new-password"
placeholder="Confirm master password"
:disabled="loading"
class="w-full px-[0.9rem] py-[0.65rem] bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] rounded-[6px] text-[var(--wraith-text-primary)] text-[0.95rem] outline-none transition-colors duration-150 box-border focus:border-[var(--wraith-accent-blue)]"
/>
<p class="mt-[0.4rem] text-[0.75rem] text-[var(--wraith-text-muted)]">
Minimum 12 characters. This password cannot be recovered.
</p>
</div>
<!-- Error message -->
<div
v-if="displayError"
class="px-[0.9rem] py-[0.6rem] bg-[rgba(248,81,73,0.1)] border border-[rgba(248,81,73,0.3)] rounded-[6px] text-[var(--wraith-accent-red)] text-[0.85rem]"
>
{{ displayError }}
</div>
<!-- Submit button -->
<button
type="submit"
:disabled="loading"
class="w-full py-[0.7rem] mt-2 bg-[var(--wraith-accent-blue)] text-[#0d1117] font-bold text-[0.9rem] tracking-[0.08em] uppercase border-none rounded-[6px] transition-[opacity,background-color] duration-150"
:class="loading ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'"
>
<span v-if="loading">
{{ isFirstRun ? "Creating vault..." : "Unlocking..." }}
</span>
<span v-else>
{{ isFirstRun ? "Create Vault" : "Unlock" }}
</span>
</button>
</form>
<!-- Footer hint -->
<p class="mt-6 text-center text-[0.75rem] text-[var(--wraith-text-muted)]">
<template v-if="isFirstRun">
Your vault will be encrypted with AES-256-GCM.
</template>
<template v-else>
All data is encrypted at rest.
</template>
</p>
</div>
</div>
</template>