- Login card 360->460px (max-width:100%), wider inputs (46px, 15px, more padding), mobile side padding; eye-toggles -> lucide Eye/EyeOff line icons (no emoji). - Setup card 380->460px (max-width:100%), wider inputs, Lock line icon instead of emoji. Rebuilt dist.
84 lines
2.8 KiB
Vue
84 lines
2.8 KiB
Vue
<template>
|
|
<div class="login-wrap">
|
|
<div class="card login-card">
|
|
<h1>{{ i18n.t('app') }}</h1>
|
|
<p class="muted" style="margin-top:-8px">Sales training simulator</p>
|
|
|
|
<label>{{ i18n.t('username') }}</label>
|
|
<input v-model="username" type="text" autocomplete="username" placeholder="username" class="wide" @keyup.enter="submit" />
|
|
|
|
<label>{{ i18n.t('password') }}</label>
|
|
<div class="pw-wrap">
|
|
<input v-model="password" :type="showPw ? 'text' : 'password'" autocomplete="current-password" placeholder="••••••••" class="wide" @keyup.enter="submit" />
|
|
<button type="button" class="pw-toggle" @click="showPw = !showPw" :aria-label="showPw ? 'Hide password' : 'Show password'">
|
|
<EyeOff v-if="showPw" :size="20" :stroke-width="1.8" />
|
|
<Eye v-else :size="20" :stroke-width="1.8" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="error" role="alert" v-if="error">{{ error }}</div>
|
|
<button class="primary" style="width:100%;margin-top:18px" :disabled="loading || !username || !password" @click="submit">
|
|
<span v-if="loading" class="spinner"></span>
|
|
<span v-else>{{ i18n.t('login') }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { Eye, EyeOff } from 'lucide-vue-next'
|
|
import { auth } from '../store/auth'
|
|
import { i18n } from '../i18n'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const username = ref('')
|
|
const password = ref('')
|
|
const showPw = ref(false)
|
|
const error = ref('')
|
|
const loading = ref(false)
|
|
|
|
async function submit() {
|
|
error.value = ''
|
|
loading.value = true
|
|
try {
|
|
await auth.login(username.value.trim(), password.value)
|
|
// First-time admin setup is mandatory before using the app.
|
|
if (auth.mustSetup) {
|
|
router.push({ path: '/setup' })
|
|
} else {
|
|
router.push(route.query.redirect || '/')
|
|
}
|
|
} catch (e) {
|
|
error.value = i18n.t('loginError')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-wrap {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding-top: 8vh;
|
|
padding-left: 16px;
|
|
padding-right: 16px;
|
|
}
|
|
/* Wider card so inputs don't truncate / cut text */
|
|
.login-card { width: 460px; max-width: 100%; padding: 28px 30px; }
|
|
h1 { margin-top: 0; font-size: 24px; }
|
|
/* Full-width, uncramped inputs (global input already width:100%) */
|
|
.wide { width: 100%; min-height: 46px; font-size: 15px; padding: 12px 14px; }
|
|
.pw-wrap { position: relative; }
|
|
.pw-wrap input { padding-right: 48px; }
|
|
.pw-toggle {
|
|
position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
|
|
background: transparent; border: none; padding: 8px; min-height: 40px; cursor: pointer;
|
|
color: var(--muted);
|
|
}
|
|
.pw-toggle:hover { color: var(--ink); }
|
|
</style>
|