Mercury/ui/src/views/Login.vue
Matthew L McPeak 271cfda84c
All checks were successful
ci / build-ui (push) Successful in 14s
ci / test (push) Successful in 2m59s
ci / publish (push) Successful in 2m24s
Initial Commit
2026-06-17 21:46:25 -04:00

173 lines
4 KiB
Vue

<template>
<div class="login-page">
<div class="login-card">
<div class="login-brand">
<img src="/favicon.svg" class="brand-ornament" alt="" />
<h1>Mercury</h1>
<p class="brand-sub">Administration Console</p>
<div class="brand-rule"></div>
</div>
<form @submit.prevent="handleLogin" class="login-form">
<div class="login-field">
<label>Username</label>
<NychInputText
v-model="username"
placeholder="username"
fluid
autocomplete="username"
/>
</div>
<div class="login-field">
<label>Password</label>
<NychInputText
v-model="password"
type="password"
placeholder="••••••••"
fluid
autocomplete="current-password"
/>
</div>
<p v-if="error" class="login-error">{{ error }}</p>
<NychButton type="submit" :loading="loading" label="Sign In" fluid />
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useRouter } from "vue-router";
import { useAuthStore } from "../stores/auth";
const auth = useAuthStore();
const router = useRouter();
const username = ref("");
const password = ref("");
const loading = ref(false);
const error = ref("");
async function handleLogin() {
loading.value = true;
error.value = "";
try {
await auth.login(username.value, password.value);
router.push("/admin/queries");
} catch {
error.value = "Invalid username or password";
} finally {
loading.value = false;
}
}
</script>
<style scoped>
.login-page {
display: flex;
min-height: 100vh;
align-items: center;
justify-content: center;
background-color: var(--surface-0);
background-image: radial-gradient(
ellipse 80% 60% at 50% 0%,
color-mix(in srgb, var(--primary) 8%, transparent),
transparent
);
}
.login-card {
width: 380px;
background: var(--surface-1);
border: 1px solid var(--border);
border-radius: 16px;
box-shadow:
0 4px 6px rgba(0, 0, 0, 0.04),
0 12px 40px rgba(0, 0, 0, 0.1),
0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent);
padding: 2.5rem 2.25rem 2.25rem;
}
.login-brand {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
margin-bottom: 2rem;
}
.brand-ornament {
color: var(--primary);
font-size: 0.9rem;
opacity: 0.7;
letter-spacing: 0.3em;
margin-bottom: 0.4rem;
max-width: 100px;
}
.login-brand h1 {
font-family: var(--font-serif);
font-size: 2.2rem;
font-weight: 400;
letter-spacing: 0.2em;
color: var(--primary);
text-transform: uppercase;
line-height: 1;
margin: 0;
}
.brand-sub {
margin: 0;
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--text-dim);
font-family: var(--font-sans);
}
.brand-rule {
width: 100%;
height: 1px;
margin-top: 1.25rem;
background: linear-gradient(
90deg,
transparent,
var(--border-hi),
transparent
);
}
.login-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.login-field {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.login-field label {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted);
}
.login-error {
margin: 0;
font-size: 0.8rem;
color: var(--danger);
text-align: center;
padding: 0.5rem;
background: var(--danger-subtle);
border: 1px solid var(--danger-border);
border-radius: 6px;
}
.nych-button-primary {
margin-top: 2px;
}
</style>