Files
thetool/public/mobile/warehouse-stocktake/components/LoginScreen.js
2026-01-13 12:44:45 +01:00

208 lines
10 KiB
JavaScript

/**
* LoginScreen Component
*
* Displays the login form for the PWA.
* Handles username/password authentication with remember me option.
*/
export default {
name: 'LoginScreen',
emits: ['login', 'set-theme'],
props: {
theme: {
type: String,
default: 'system'
}
},
setup(props, { emit }) {
const { ref } = Vue;
// Form state
const username = ref('');
const password = ref('');
const rememberMe = ref(true);
const error = ref('');
const loading = ref(false);
const showPassword = ref(false);
// Theme picker (shown on first visit)
const showThemePicker = ref(!localStorage.getItem('theme'));
const handleSubmit = async () => {
if (!username.value || !password.value) {
error.value = 'Bitte Benutzername und Passwort eingeben';
return;
}
loading.value = true;
error.value = '';
try {
const result = await new Promise((resolve) => {
// Emit returns undefined, we need to wait for parent to call back
const loginPromise = emit('login', {
username: username.value,
password: password.value,
rememberMe: rememberMe.value
});
// The parent will return the result
resolve(loginPromise);
});
if (result && !result.success) {
error.value = result.message || 'Login fehlgeschlagen';
if (result.requires2FA) {
error.value = 'Zwei-Faktor-Authentifizierung wird derzeit nicht unterstützt.';
}
}
} catch (e) {
error.value = 'Ein Fehler ist aufgetreten';
} finally {
loading.value = false;
}
};
const selectTheme = (newTheme) => {
emit('set-theme', newTheme);
showThemePicker.value = false;
};
return {
username,
password,
rememberMe,
error,
loading,
showPassword,
showThemePicker,
handleSubmit,
selectTheme
};
},
template: `
<div class="min-h-screen flex items-center justify-center bg-slate-100 dark:bg-slate-900 p-4">
<!-- Theme Picker Modal (First Visit) -->
<transition name="fade">
<div v-if="showThemePicker" class="fixed inset-0 bg-black bg-opacity-60 z-50 flex items-center justify-center p-4">
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 w-full max-w-xs text-center shadow-2xl">
<h3 class="font-bold text-lg mb-2 text-slate-800 dark:text-white">Willkommen!</h3>
<p class="text-sm text-slate-600 dark:text-slate-300 mb-6">Wähle dein bevorzugtes Farbschema.</p>
<div class="flex flex-col space-y-3">
<button @click="selectTheme('light')" class="w-full px-4 py-3 bg-slate-200 text-slate-800 font-bold rounded-md hover:bg-slate-300 transition">
Hell
</button>
<button @click="selectTheme('dark')" class="w-full px-4 py-3 bg-slate-700 text-white font-bold rounded-md hover:bg-slate-600 transition">
Dunkel
</button>
<button @click="selectTheme('system')" class="w-full mt-2 text-sm text-slate-500 dark:text-slate-400 hover:underline">
Systemstandard
</button>
</div>
</div>
</div>
</transition>
<!-- Login Form -->
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 w-full max-w-sm">
<!-- Logo -->
<div class="mb-8">
<img src="/assets/images/xinon-full-transparent.png" class="h-10 mx-auto dark:hidden" alt="Logo">
<img src="/assets/images/xinon-full-transparent-white.png" class="h-10 mx-auto hidden dark:block" alt="Logo">
</div>
<!-- Title -->
<h1 class="text-xl font-bold text-center text-slate-800 dark:text-white mb-6">
Lager Inventur
</h1>
<!-- Form -->
<form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Username -->
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Benutzername
</label>
<input
v-model="username"
type="text"
autocomplete="username"
autocapitalize="none"
class="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition dark:bg-slate-700 dark:border-slate-600 dark:text-white"
placeholder="Benutzername eingeben"
>
</div>
<!-- Password -->
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Passwort
</label>
<div class="relative">
<input
v-model="password"
:type="showPassword ? 'text' : 'password'"
autocomplete="current-password"
class="w-full p-3 pr-12 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition dark:bg-slate-700 dark:border-slate-600 dark:text-white"
placeholder="Passwort eingeben"
>
<button
type="button"
@click="showPassword = !showPassword"
class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300"
>
<svg v-if="!showPassword" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
</div>
</div>
<!-- Remember Me -->
<label class="flex items-center cursor-pointer">
<input
v-model="rememberMe"
type="checkbox"
class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary dark:border-slate-600 dark:bg-slate-700"
>
<span class="ml-2 text-sm text-slate-600 dark:text-slate-300">
Angemeldet bleiben
</span>
</label>
<!-- Error Message -->
<div v-if="error" class="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-lg">
<p class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
</div>
<!-- Submit Button -->
<button
type="submit"
:disabled="loading"
class="w-full py-3 px-4 bg-primary text-white font-bold rounded-lg hover:bg-primary/90 focus:ring-4 focus:ring-primary/30 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
>
<svg v-if="loading" class="animate-spin -ml-1 mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{{ loading ? 'Wird angemeldet...' : 'Anmelden' }}
</button>
</form>
<!-- Footer -->
<div class="mt-8 text-center">
<p class="text-xs text-slate-400 dark:text-slate-500">
powered by XINON GmbH
</p>
</div>
</div>
</div>
`
};