fix: login diseño
This commit is contained in:
parent
0ffa93019c
commit
b1d0f73e94
@ -4,7 +4,6 @@ import { APP_VERSION, APP_COPYRIGHT } from '@/config.js'
|
||||
import useDarkMode from '@Stores/DarkMode'
|
||||
|
||||
import Logo from '@Holos/Logo.vue'
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
|
||||
/** Definidores */
|
||||
const darkMode = useDarkMode()
|
||||
@ -21,49 +20,56 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen flex bg-primary dark:bg-primary-d">
|
||||
<div
|
||||
class="relative flex w-full lg:w-full justify-around items-center with-transition"
|
||||
:class="{'app-bg-light':darkMode.isLight,'app-bg-dark':darkMode.isDark}"
|
||||
>
|
||||
<header class="absolute top-0 flex w-full h-8 px-1 items-center justify-end text-white">
|
||||
<div>
|
||||
<IconButton v-if="darkMode.isLight"
|
||||
icon="light_mode"
|
||||
:title="$t('app.theme.light')"
|
||||
@click="darkMode.applyDark()"
|
||||
/>
|
||||
<IconButton v-else
|
||||
icon="dark_mode"
|
||||
:title="$t('app.theme.dark')"
|
||||
@click="darkMode.applyLight()"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex w-full flex-col items-center justify-center space-y-2">
|
||||
<div class="flex space-x-2 items-center justify-start text-white">
|
||||
<Logo
|
||||
class="text-lg inline-flex"
|
||||
/>
|
||||
<div class="min-h-screen flex">
|
||||
<!-- Panel Izquierdo - Branding -->
|
||||
<div class="hidden lg:flex lg:w-1/2 bg-primary dark:bg-primary-d relative overflow-hidden">
|
||||
<!-- Fondo con patron sutil -->
|
||||
<div class="absolute inset-0 opacity-5">
|
||||
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 25px 25px, white 2px, transparent 0); background-size: 50px 50px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Contenido centrado -->
|
||||
<div class="relative z-10 flex flex-col items-center justify-center w-full px-12 text-white">
|
||||
<!-- Logo/Icono -->
|
||||
<div class="mb-8">
|
||||
<Logo size="xl" class="text-lg inline-flex" />
|
||||
</div>
|
||||
|
||||
<!-- Titulo -->
|
||||
<h1 class="text-3xl font-bold text-center mb-4">
|
||||
Portal Administrativo
|
||||
</h1>
|
||||
|
||||
<!-- Descripcion -->
|
||||
<p class="text-white/70 text-center max-w-sm mb-8">
|
||||
Sistema de Gestion de Inventario y Ventas.<br>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel Derecho - Formulario -->
|
||||
<div class="w-full lg:w-1/2 bg-page dark:bg-page-d flex flex-col">
|
||||
<!-- Contenido principal -->
|
||||
<div class="flex-1 flex items-center justify-center px-6 py-12">
|
||||
<div class="w-full max-w-md">
|
||||
<!-- Logo mobile -->
|
||||
<div class="lg:hidden flex justify-center mb-8">
|
||||
<div class="w-16 h-16 bg-primary dark:bg-primary-d rounded-xl flex items-center justify-center">
|
||||
<Logo class="scale-50" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="bg-white/10 w-full backdrop-blur-xs text-white px-4 py-4 rounded-sm max-w-80">
|
||||
<RouterView />
|
||||
</main>
|
||||
|
||||
<footer class="absolute bottom-0 flex w-full h-8 px-4 items-center justify-between bg-primary dark:bg-primary-d backdrop-blur-md text-white transition-colors duration-global">
|
||||
<div>
|
||||
<span>
|
||||
©2024 {{ APP_COPYRIGHT }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
APP {{ APP_VERSION }} API {{ $page.app.version }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Footer del panel derecho -->
|
||||
<footer class="py-4 px-6 border-t border-primary/10 dark:border-primary-dt/10">
|
||||
<div class="flex items-center justify-between text-xs text-page-t/50 dark:text-page-dt/50">
|
||||
<span>© {{ new Date().getFullYear() }} {{ APP_COPYRIGHT }}</span>
|
||||
<span>v{{ APP_VERSION }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { defineApiToken, defineCsrfToken, hasToken, useForm } from '@Services/Api.js'
|
||||
import { defineUser } from '@Services/Page';
|
||||
import { viewTo } from './Module.js';
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue'
|
||||
import Input from '@Holos/Form/InputWithIcon.vue'
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue'
|
||||
import Error from '@Holos/Form/Elements/Error.vue'
|
||||
|
||||
/** Definidores */
|
||||
const router = useRouter();
|
||||
@ -22,19 +23,29 @@ const form = useForm({
|
||||
password: ''
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const isLoading = ref(false);
|
||||
|
||||
/** Métodos */
|
||||
const login = () => {
|
||||
isLoading.value = true;
|
||||
form.post(route('auth.login'), {
|
||||
onSuccess: (res) => {
|
||||
defineApiToken(res.token)
|
||||
defineUser(res.user)
|
||||
defineCsrfToken(res.csrf)
|
||||
|
||||
location.replace('/')
|
||||
},
|
||||
onFinish: () => {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value;
|
||||
};
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
if (hasToken()) {
|
||||
@ -44,33 +55,100 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="login">
|
||||
<Input
|
||||
icon="mail"
|
||||
<div class="w-full">
|
||||
<!-- Header del formulario -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-page-t dark:text-page-dt mb-2">
|
||||
Iniciar Sesion
|
||||
</h1>
|
||||
<p class="text-page-t/60 dark:text-page-dt/60 text-sm">
|
||||
Ingrese sus credenciales.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="login" class="space-y-6">
|
||||
<!-- Campo Email/Usuario -->
|
||||
<div class="space-y-2">
|
||||
<label for="email" class="block text-xs font-semibold text-page-t/70 dark:text-page-dt/70 uppercase tracking-wider">
|
||||
Usuario / ID
|
||||
</label>
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
<GoogleIcon
|
||||
name="person"
|
||||
class="text-page-t/40 dark:text-page-dt/40 group-focus-within:text-primary dark:group-focus-within:text-primary-dt transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
placeholder="ID de empleado"
|
||||
class="w-full pl-12 pr-4 py-3.5 bg-page dark:bg-page-d border border-primary/20 dark:border-primary-dt/20 rounded-lg
|
||||
text-page-t dark:text-page-dt placeholder-page-t/40 dark:placeholder-page-dt/40
|
||||
focus:outline-none focus:border-primary/50 dark:focus:border-primary-dt/50 focus:ring-2 focus:ring-primary/10 dark:focus:ring-primary-dt/10
|
||||
hover:border-primary/30 dark:hover:border-primary-dt/30 transition-all duration-200"
|
||||
:class="{ 'border-danger! focus:border-danger! focus:ring-danger/20!': form.errors.email }"
|
||||
autocomplete="email"
|
||||
/>
|
||||
<Input
|
||||
v-model="form.password"
|
||||
icon="password"
|
||||
id="password"
|
||||
type="password"
|
||||
:onError="form.errors.password"
|
||||
:placeholder="$t('password')"
|
||||
/>
|
||||
<PrimaryButton class="!w-full">
|
||||
{{ $t('auth.login') }}
|
||||
</PrimaryButton>
|
||||
<div class="flex justify-end mt-4">
|
||||
<RouterLink
|
||||
class="text-sm ml-2 hover:text-blue-200 cursor-pointer hover:-translate-y-1 duration-500 transition-all"
|
||||
:to="viewTo({ name: 'forgot-password' })"
|
||||
>
|
||||
{{ $t('auth.forgotPassword.ask') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<Error :onError="form.errors.email" />
|
||||
</div>
|
||||
|
||||
<!-- Campo Password -->
|
||||
<div class="space-y-2">
|
||||
<label for="password" class="block text-xs font-semibold text-page-t/70 dark:text-page-dt/70 uppercase tracking-wider">
|
||||
Contraseña
|
||||
</label>
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
<GoogleIcon
|
||||
name="lock"
|
||||
class="text-page-t/40 dark:text-page-dt/40 group-focus-within:text-primary dark:group-focus-within:text-primary-dt transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
v-model="form.password"
|
||||
placeholder="••••••••••"
|
||||
class="w-full pl-12 pr-12 py-3.5 bg-page dark:bg-page-d border border-primary/20 dark:border-primary-dt/20 rounded-lg
|
||||
text-page-t dark:text-page-dt placeholder-page-t/40 dark:placeholder-page-dt/40
|
||||
focus:outline-none focus:border-primary/50 dark:focus:border-primary-dt/50 focus:ring-2 focus:ring-primary/10 dark:focus:ring-primary-dt/10
|
||||
hover:border-primary/30 dark:hover:border-primary-dt/30 transition-all duration-200"
|
||||
:class="{ 'border-danger! focus:border-danger! focus:ring-danger/20!': form.errors.password }"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="togglePassword"
|
||||
class="absolute inset-y-0 right-0 pr-4 flex items-center text-page-t/40 dark:text-page-dt/40 hover:text-page-t dark:hover:text-page-dt transition-colors"
|
||||
>
|
||||
<GoogleIcon :name="showPassword ? 'visibility_off' : 'visibility'" />
|
||||
</button>
|
||||
</div>
|
||||
<Error :onError="form.errors.password" />
|
||||
</div>
|
||||
|
||||
<!-- Boton de Login -->
|
||||
<PrimaryButton
|
||||
class="w-full! py-4! text-sm! font-semibold! rounded-lg! mt-2!
|
||||
bg-primary! hover:bg-primary/90! dark:bg-primary-d! dark:hover:bg-primary-d/90!
|
||||
transition-all duration-200"
|
||||
:disabled="isLoading || form.processing"
|
||||
:class="{ 'opacity-70 cursor-not-allowed': isLoading || form.processing }"
|
||||
>
|
||||
<span v-if="isLoading || form.processing" class="flex items-center justify-center gap-2">
|
||||
<svg class="animate-spin h-5 w-5" 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>
|
||||
Verificando...
|
||||
</span>
|
||||
<span v-else>
|
||||
Acceder al Sistema
|
||||
</span>
|
||||
</PrimaryButton>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user