login modificado primera version

This commit is contained in:
Abraham
2025-07-15 22:41:40 -07:00
parent 36b5757041
commit 07395137a5
6 changed files with 404 additions and 264 deletions

BIN
assets/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/images/logo_nh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

BIN
assets/images/logo_nh_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 KiB

View File

@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:nethive_neo/models/users/token.dart';
import 'package:nethive_neo/pages/login_page/widgets/login_form.dart';
import 'package:nethive_neo/pages/login_page/widgets/right_image.dart';
import 'package:nethive_neo/theme/theme.dart';
class LoginPage extends StatefulWidget {
const LoginPage({
@@ -23,51 +21,169 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
key: scaffoldKey,
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return Padding(
padding: const EdgeInsets.all(20),
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: AppTheme.of(context).primaryBackground,
borderRadius: BorderRadius.circular(19),
if (constraints.maxWidth < 768) {
// Vista móvil - fondo degradado completo
return Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color(0xFF22C55E), // Verde izquierda
Color(0xFF3B82F6), // Azul centro
Color(0xFF8B5CF6), // Morado derecha
],
),
child: Center(
child: LoginForm(),
),
child: SingleChildScrollView(
child: Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height,
),
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo NetHive para móvil
Container(
margin: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Image.asset(
'assets/images/favicon.png',
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 16),
const Text(
'NetHive',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Text(
'Infrastructure Management',
style: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
],
),
),
const LoginForm(),
],
),
),
),
);
} else {
return Padding(
padding: const EdgeInsets.all(10),
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: AppTheme.of(context).primaryBackground,
borderRadius: BorderRadius.circular(19),
// Vista desktop - columna izquierda sólida, derecha degradado
return Row(
children: [
// Lado izquierdo - Formulario (fondo sólido)
Expanded(
flex: 1,
child: Container(
color:
const Color(0xFF1E293B), // Fondo sólido azul oscuro
padding: const EdgeInsets.symmetric(
horizontal: 60, vertical: 40),
child: Center(
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: const LoginForm(),
),
),
),
),
const Positioned(
top: 101,
left: 109,
child: LoginForm(),
),
// Lado derecho - Logo (con degradado)
Expanded(
flex: 1,
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color(0xFF22C55E), // Verde izquierda
Color(0xFF3B82F6), // Azul centro
Color(0xFF8B5CF6), // Morado derecha
],
),
),
padding: const EdgeInsets.all(60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Logo NetHive
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Image.asset(
'assets/images/favicon.png',
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 32),
const Text(
'NetHive',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
const Text(
'Infrastructure Management',
style: TextStyle(
fontSize: 18,
color: Colors.white70,
fontWeight: FontWeight.w300,
),
),
],
),
),
const Positioned(
right: 0,
child: RightImageWidget(),
)
],
),
),
],
);
}
},

View File

@@ -9,7 +9,6 @@ import 'package:provider/provider.dart';
import 'package:nethive_neo/helpers/globals.dart';
import 'package:nethive_neo/helpers/supabase/queries.dart';
import 'package:nethive_neo/pages/widgets/custom_button.dart';
import 'package:nethive_neo/providers/providers.dart';
import 'package:nethive_neo/services/api_error_handler.dart';
import 'package:nethive_neo/theme/theme.dart';
@@ -28,8 +27,6 @@ class _LoginFormState extends State<LoginForm> {
@override
Widget build(BuildContext context) {
final UserState userState = Provider.of<UserState>(context);
double height = MediaQuery.of(context).size.height / 1024;
double width = MediaQuery.of(context).size.width / 1440;
Future<void> login() async {
//Login
@@ -101,257 +98,284 @@ class _LoginFormState extends State<LoginForm> {
}
}
return SizedBox(
width: 521,
child: SingleChildScrollView(
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Image.asset(
AppTheme.themeMode == ThemeMode.light
? 'assets/images/logo_lu.jpeg'
: 'assets/images/logo_lu.jpeg',
filterQuality: FilterQuality.high,
fit: BoxFit.contain,
alignment: Alignment.centerLeft,
width: width * 200,
height: height * 200,
return Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 400),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Logo NetHive para formulario (solo en desktop)
MediaQuery.of(context).size.width >= 768
? Container(
margin: const EdgeInsets.only(bottom: 40),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF3B82F6),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(8),
child: Image.asset(
'assets/images/favicon.png',
fit: BoxFit.contain,
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bienvenido a NetHive',
style: GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'Plataforma de Gestión de Infraestructura',
style: GoogleFonts.inter(
fontSize: 14,
color: Colors.white70,
),
),
],
),
],
),
)
: const SizedBox(),
// Título
Text(
'CORREO ELECTRÓNICO',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
),
),
const SizedBox(height: 8),
// Campo de email
TextFormField(
controller: userState.emailController,
onFieldSubmitted: (value) async {
if (!formKey.currentState!.validate()) {
return;
}
await login();
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'El correo es requerido';
} else if (!EmailValidator.validate(value)) {
return 'Favor de ingresar un correo valido';
}
return null;
},
style: GoogleFonts.inter(
color: Colors.white,
fontSize: 16,
),
decoration: InputDecoration(
hintText: 'admin@nethive.com',
hintStyle: GoogleFonts.inter(
color: Colors.white54,
fontSize: 16,
),
filled: true,
fillColor: Colors.white.withOpacity(0.1),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
),
Text(
'Inicie Sesión',
textAlign: TextAlign.start,
style: AppTheme.of(context).title3.override(
fontFamily: AppTheme.of(context).title3Family,
color: AppTheme.of(context).primaryColor,
),
),
const SizedBox(height: 20),
// Título contraseña
Text(
'CONTRASEÑA',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
),
const SizedBox(height: 50.32),
Text(
'Correo',
style: AppTheme.of(context).bodyText2,
),
const SizedBox(height: 8),
// Campo de contraseña
TextFormField(
controller: userState.passwordController,
obscureText: !passwordVisibility,
onFieldSubmitted: (value) async {
if (!formKey.currentState!.validate()) {
return;
}
await login();
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'La contraseña es requerida';
}
return null;
},
style: GoogleFonts.inter(
color: Colors.white,
fontSize: 16,
),
const SizedBox(height: 20),
TextFormField(
controller: userState.emailController,
onFieldSubmitted: (value) async {
if (!formKey.currentState!.validate()) {
return;
}
await login();
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'El correo es requerido';
} else if (!EmailValidator.validate(value)) {
return 'Favor de ingresar un correo valido';
}
return null;
},
decoration: _buildInputDecoration('Nombre de usuario'),
style: AppTheme.of(context).bodyText3.override(
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).primaryText,
),
),
const SizedBox(height: 16),
Text(
'Contraseña',
style: AppTheme.of(context).bodyText2,
),
const SizedBox(height: 20),
TextFormField(
controller: userState.passwordController,
obscureText: !passwordVisibility,
onFieldSubmitted: (value) async {
if (!formKey.currentState!.validate()) {
return;
}
await login();
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'La contraseña es requerida';
}
return null;
},
decoration:
_buildInputDecoration('Contraseña', isPassword: true),
style: AppTheme.of(context).bodyText3.override(
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).primaryText,
),
),
const SizedBox(height: 53),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Transform.scale(
scale: 1.25,
child: Checkbox(
value: userState.recuerdame,
activeColor: AppTheme.of(context).primaryColor,
onChanged: (value) async {
await userState.updateRecuerdame();
},
splashRadius: 0,
),
decoration: InputDecoration(
hintText: '••••••',
hintStyle: GoogleFonts.inter(
color: Colors.white54,
fontSize: 16,
),
filled: true,
fillColor: Colors.white.withOpacity(0.1),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
suffixIcon: IconButton(
icon: Icon(
passwordVisibility
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
color: Colors.white54,
),
const SizedBox(width: 18),
InkWell(
onTap: () async {
await userState.updateRecuerdame();
},
child: Text(
'Recuerdame',
style: AppTheme.of(context).bodyText3,
),
onPressed: () => setState(
() => passwordVisibility = !passwordVisibility,
),
const Spacer(),
InkWell(
onTap: () {},
child: Text(
'Olvidaste Contraseña?',
style: AppTheme.of(context).bodyText3.override(
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).primaryColor,
),
),
),
],
),
),
const SizedBox(height: 20),
CustomButton(
),
const SizedBox(height: 32),
// Botón de iniciar sesión
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () async {
if (!formKey.currentState!.validate()) {
return;
}
await login();
},
text: 'Iniciar Sesión',
options: ButtonOptions(
width: double.infinity,
height: 68,
color: AppTheme.of(context).primaryColor,
textStyle: AppTheme.of(context).bodyText2.override(
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).primaryBackground,
),
borderRadius: BorderRadius.circular(6),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF22C55E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 0,
),
child: Text(
'INICIAR SESIÓN',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
),
),
),
const SizedBox(height: 68),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'No registrasdo aun? ',
style: AppTheme.of(context).bodyText2.override(
fontFamily: AppTheme.of(context).bodyText2Family,
color: AppTheme.of(context).alternate,
),
),
const SizedBox(height: 12),
// Enlace "Conexión insegura"
Center(
child: TextButton(
onPressed: () {},
child: Text(
'Conexión insegura',
style: GoogleFonts.inter(
color: const Color(0xFFEF4444),
fontSize: 13,
decoration: TextDecoration.underline,
decorationColor: const Color(0xFFEF4444),
),
InkWell(
onTap: () {},
child: Text(
'Registrate',
style: AppTheme.of(context).bodyText2.override(
fontFamily: AppTheme.of(context).bodyText2Family,
color: AppTheme.of(context).primaryColor,
),
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.copyright,
size: 16,
color: Color(0xFF99B2C6),
),
const SizedBox(width: 12),
Text(
'Copyright CB Luna 2024',
style: AppTheme.of(context).bodyText3.override(
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).alternate,
),
),
],
),
const SizedBox(height: 40),
// Características principales
Text(
'CARACTERÍSTICAS PRINCIPALES',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
),
],
),
),
const SizedBox(height: 16),
// Lista de características
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildFeatureItem('Gestión completa de infraestructura'),
_buildFeatureItem('Monitoreo en tiempo real'),
_buildFeatureItem('Reportes avanzados'),
_buildFeatureItem('Dashboard intuitivo'),
],
),
],
),
),
);
}
InputDecoration _buildInputDecoration(String label,
{bool isPassword = false}) {
Widget? suffixIcon;
if (isPassword) {
suffixIcon = InkWell(
onTap: () => setState(
() => passwordVisibility = !passwordVisibility,
),
focusNode: FocusNode(skipTraversal: true),
child: Icon(
passwordVisibility
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
color: const Color(0xFFB8B8B8),
size: 22,
),
);
}
return InputDecoration(
hintText: label,
filled: true,
// isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 27, vertical: 20),
fillColor: AppTheme.of(context).tertiaryBackground,
hintStyle: GoogleFonts.quicksand(
fontWeight: FontWeight.w600,
fontSize: 15,
color: AppTheme.of(context).hintText,
Widget _buildFeatureItem(String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 6, right: 12),
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Color(0xFF22C55E),
shape: BoxShape.circle,
),
),
Expanded(
child: Text(
text,
style: GoogleFonts.inter(
color: Colors.white70,
fontSize: 14,
height: 1.5,
),
),
),
],
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Colors.transparent,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Colors.transparent,
width: 1,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Colors.red,
width: 1,
),
),
prefixIcon: Padding(
padding: const EdgeInsets.only(left: 27, right: 19),
child: Icon(
isPassword ? Icons.lock : Icons.mail_rounded,
size: 24,
color: AppTheme.of(context).hintText,
),
),
suffixIcon: suffixIcon,
);
}
}