feat: rediseñar página de login con glassmorphism y diseño responsive

- Rediseño completo de Login.vue con burbujas animadas de colores
- Implementado glassmorphism (backdrop-filter blur) en tarjeta de login
- Migrado a Composition API con <script setup>
- Formulario mejorado: iconos mdi, variant outlined, loading state
- Diseño responsive con breakpoints mobile/tablet/desktop
- Burbuja amarilla con opacidad reducida (0.08-0.25) para mejor UX
- Fix: agregado import en autenticarse.vue
This commit is contained in:
2026-05-29 16:39:46 -05:00
parent 368b7007f6
commit a36fbd289e
2 changed files with 259 additions and 57 deletions

View File

@@ -1,70 +1,272 @@
<template>
<h1>Login</h1>
<v-container fluid class="pa-0">
<v-sheet class="hero-section d-flex align-center justify-center">
<div class="glow-bubble bubble-blue"></div>
<div class="glow-bubble bubble-green"></div>
<div class="glow-bubble bubble-yellow"></div>
<div class="glow-bubble bubble-red"></div>
<v-form ref="loginForm" @submit.prevent="onSubmit">
<v-text-field
v-model="username"
label="Usuario"
:rules="[requiredRule]"
required
/>
<v-text-field
v-model="password"
label="Contraseña"
type="password"
:rules="[requiredRule]"
required
/>
<div class="login-card">
<v-img
:src="logo"
alt="Don Confiao"
max-width="140"
class="mx-auto mb-4"
/>
<h1 class="text-h5 text-sm-h4 font-weight-bold text-center mb-1">
Iniciar Sesión
</h1>
<p class="text-body-2 text-medium-emphasis text-center mb-6">
Ingresa tus credenciales para acceder
</p>
<v-btn type="submit" color="primary">Entrar</v-btn>
<v-form ref="loginForm" @submit.prevent="onSubmit">
<v-text-field
v-model="username"
label="Usuario"
prepend-inner-icon="mdi-account"
:rules="[requiredRule]"
variant="outlined"
required
class="mb-2"
autocomplete="username"
/>
<v-text-field
v-model="password"
label="Contraseña"
prepend-inner-icon="mdi-lock"
type="password"
:rules="[requiredRule]"
variant="outlined"
required
class="mb-4"
autocomplete="current-password"
/>
<v-alert v-if="error" type="error" class="mt-2">{{ error }}</v-alert>
</v-form>
<v-btn
type="submit"
color="primary"
size="large"
block
:loading="isSubmitting"
:disabled="isSubmitting"
>
Entrar
</v-btn>
<v-alert
v-if="error"
type="error"
variant="tonal"
class="mt-4"
closable
@click:close="error = ''"
>
{{ error }}
</v-alert>
</v-form>
</div>
</v-sheet>
</v-container>
</template>
<script>
import AuthService from '@/services/auth';
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import AuthService from '@/services/auth'
import logo from '@/assets/logo_colorful.png'
export default {
name: 'DonConfiao',
const router = useRouter()
data() {
return {
username: '',
password: '',
error: '',
};
},
const username = ref('')
const password = ref('')
const error = ref('')
const isSubmitting = ref(false)
const loginForm = ref(null)
methods: {
requiredRule(value) {
return !!value || 'Este campo es obligatorio';
},
function requiredRule(value) {
return !!value || 'Este campo es obligatorio'
}
async onSubmit() {
this.error = '';
async function onSubmit() {
error.value = ''
isSubmitting.value = true
const form = this.$refs.loginForm;
const isValid = await form.validate();
try {
const form = loginForm.value
if (form) {
const { valid } = await form.validate()
if (!valid) return
}
if (!isValid) return;
if (!username.value || !password.value) {
error.value = 'Usuario y contraseña son obligatorios'
return
}
if (!this.username || !this.password) {
this.error = 'Usuario y contraseña son obligatorios';
return;
}
try {
await AuthService.login({
username: this.username,
password: this.password,
});
this.$router.push({ path: '/' });
} catch (e) {
const msg = e?.response?.data?.message ?? e.message;
this.error = msg ?? 'Error al iniciar sesión';
}
},
},
};
await AuthService.login({
username: username.value,
password: password.value,
})
router.push({ path: '/' })
} catch (e) {
const msg = e?.response?.data?.message ?? e.message
error.value = msg ?? 'Error al iniciar sesión'
} finally {
isSubmitting.value = false
}
}
</script>
<style scoped>
.hero-section {
min-height: calc(100vh - 80px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
position: relative;
overflow: hidden;
background-color: #f8fafc !important;
}
.glow-bubble {
position: absolute;
border-radius: 10%;
pointer-events: none;
z-index: 1;
mix-blend-mode: normal;
}
.bubble-blue {
width: 500px;
height: 500px;
background: radial-gradient(
circle,
rgba(66, 165, 245, 0.8) 10%,
rgba(66, 165, 245, 0) 80%
);
filter: blur(100px);
top: -180px;
left: -150px;
animation: floatCornerTL 8s ease-in-out infinite;
}
.bubble-green {
width: 500px;
height: 500px;
background: radial-gradient(
circle,
rgba(0, 200, 83, 0.7) 10%,
rgba(0, 200, 83, 0) 80%
);
filter: blur(100px);
bottom: -150px;
right: -120px;
animation: floatCornerBR 9s ease-in-out infinite;
animation-delay: 1.5s;
}
.bubble-yellow {
width: 500px;
height: 500px;
background: radial-gradient(
circle,
rgba(255, 213, 0, 0.3) 20%,
rgba(255, 193, 7, 0) 1000%
);
filter: blur(80px);
top: 50%;
left: 50%;
animation: glowPulseCenter 8s ease-in-out infinite;
}
.bubble-red {
width: 500px;
height: 500px;
background: radial-gradient(
circle,
rgba(239, 83, 80, 0.7) 10%,
rgba(239, 83, 80, 0) 80%
);
filter: blur(100px);
top: -150px;
right: -120px;
animation: floatCornerTR 8s ease-in-out infinite;
animation-delay: 3s;
}
.login-card {
position: relative;
z-index: 2;
width: 100%;
max-width: 420px;
padding: 2.5rem 2rem;
background: rgba(255, 255, 255, 0.25) !important;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 10px 40px -10px rgba(15, 23, 42, 0.08);
}
@media (max-width: 600px) {
.hero-section {
padding: 1rem;
min-height: calc(100vh - 64px);
}
.login-card {
padding: 1.5rem 1.25rem;
border-radius: 20px;
}
}
@keyframes floatCornerTL {
0%,
100% {
opacity: 0.15;
transform: scale(0.9) translate(0, 0);
}
50% {
opacity: 0.85;
transform: scale(1.25) translate(40px, 30px);
}
}
@keyframes floatCornerTR {
0%,
100% {
opacity: 0.15;
transform: scale(1.2) translate(0, 0);
}
50% {
opacity: 0.8;
transform: scale(0.95) translate(-30px, 40px);
}
}
@keyframes floatCornerBR {
0%,
100% {
opacity: 0.1;
transform: scale(0.85) translate(0, 0);
}
50% {
opacity: 0.75;
transform: scale(1.15) translate(-40px, -30px);
}
}
@keyframes glowPulseCenter {
0%,
100% {
opacity: 0.08;
transform: translate(-50%, -50%) scale(0.85);
}
50% {
opacity: 0.25;
transform: translate(-50%, -50%) scale(1.3);
}
}
</style>

View File

@@ -3,5 +3,5 @@
</template>
<script setup>
//
import Login from '@/components/Login.vue'
</script>