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/models/users/token.dart';
import 'package:nethive_neo/pages/login_page/widgets/login_form.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 { class LoginPage extends StatefulWidget {
const LoginPage({ const LoginPage({
@@ -23,51 +21,169 @@ class _LoginPageState extends State<LoginPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
key: scaffoldKey, key: scaffoldKey,
body: GestureDetector( body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), onTap: () => FocusScope.of(context).unfocus(),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (constraints.maxWidth < 600) { if (constraints.maxWidth < 768) {
return Padding( // Vista móvil - fondo degradado completo
padding: const EdgeInsets.all(20), return Container(
child: Container( width: double.infinity,
width: double.infinity, height: double.infinity,
height: double.infinity, decoration: const BoxDecoration(
decoration: BoxDecoration( gradient: LinearGradient(
color: AppTheme.of(context).primaryBackground, begin: Alignment.centerLeft,
borderRadius: BorderRadius.circular(19), 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 { } else {
return Padding( // Vista desktop - columna izquierda sólida, derecha degradado
padding: const EdgeInsets.all(10), return Row(
child: Stack( children: [
children: [ // Lado izquierdo - Formulario (fondo sólido)
Container( Expanded(
width: double.infinity, flex: 1,
height: double.infinity, child: Container(
decoration: BoxDecoration( color:
color: AppTheme.of(context).primaryBackground, const Color(0xFF1E293B), // Fondo sólido azul oscuro
borderRadius: BorderRadius.circular(19), 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, // Lado derecho - Logo (con degradado)
left: 109, Expanded(
child: LoginForm(), 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/globals.dart';
import 'package:nethive_neo/helpers/supabase/queries.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/providers/providers.dart';
import 'package:nethive_neo/services/api_error_handler.dart'; import 'package:nethive_neo/services/api_error_handler.dart';
import 'package:nethive_neo/theme/theme.dart'; import 'package:nethive_neo/theme/theme.dart';
@@ -28,8 +27,6 @@ class _LoginFormState extends State<LoginForm> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final UserState userState = Provider.of<UserState>(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 { Future<void> login() async {
//Login //Login
@@ -101,257 +98,284 @@ class _LoginFormState extends State<LoginForm> {
} }
} }
return SizedBox( return Container(
width: 521, width: double.infinity,
child: SingleChildScrollView( constraints: const BoxConstraints(maxWidth: 400),
child: Form( child: Form(
key: formKey, key: formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
children: [ // Logo NetHive para formulario (solo en desktop)
Center( MediaQuery.of(context).size.width >= 768
child: Image.asset( ? Container(
AppTheme.themeMode == ThemeMode.light margin: const EdgeInsets.only(bottom: 40),
? 'assets/images/logo_lu.jpeg' child: Row(
: 'assets/images/logo_lu.jpeg', children: [
filterQuality: FilterQuality.high, Container(
fit: BoxFit.contain, width: 40,
alignment: Alignment.centerLeft, height: 40,
width: width * 200, decoration: BoxDecoration(
height: height * 200, 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, const SizedBox(height: 20),
style: AppTheme.of(context).title3.override(
fontFamily: AppTheme.of(context).title3Family, // Título contraseña
color: AppTheme.of(context).primaryColor, Text(
), 'CONTRASEÑA',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
), ),
const SizedBox(height: 50.32), ),
Text( const SizedBox(height: 8),
'Correo',
style: AppTheme.of(context).bodyText2, // 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), decoration: InputDecoration(
TextFormField( hintText: '••••••',
controller: userState.emailController, hintStyle: GoogleFonts.inter(
onFieldSubmitted: (value) async { color: Colors.white54,
if (!formKey.currentState!.validate()) { fontSize: 16,
return; ),
} filled: true,
await login(); fillColor: Colors.white.withOpacity(0.1),
}, border: OutlineInputBorder(
validator: (value) { borderRadius: BorderRadius.circular(8),
if (value == null || value.isEmpty) { borderSide: BorderSide.none,
return 'El correo es requerido'; ),
} else if (!EmailValidator.validate(value)) { contentPadding: const EdgeInsets.symmetric(
return 'Favor de ingresar un correo valido'; horizontal: 16,
} vertical: 16,
return null; ),
}, suffixIcon: IconButton(
decoration: _buildInputDecoration('Nombre de usuario'), icon: Icon(
style: AppTheme.of(context).bodyText3.override( passwordVisibility
fontFamily: AppTheme.of(context).bodyText3Family, ? Icons.visibility_outlined
color: AppTheme.of(context).primaryText, : Icons.visibility_off_outlined,
), color: Colors.white54,
),
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,
),
), ),
const SizedBox(width: 18), onPressed: () => setState(
InkWell( () => passwordVisibility = !passwordVisibility,
onTap: () async {
await userState.updateRecuerdame();
},
child: Text(
'Recuerdame',
style: AppTheme.of(context).bodyText3,
),
), ),
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 { onPressed: () async {
if (!formKey.currentState!.validate()) { if (!formKey.currentState!.validate()) {
return; return;
} }
await login(); await login();
}, },
text: 'Iniciar Sesión', style: ElevatedButton.styleFrom(
options: ButtonOptions( backgroundColor: const Color(0xFF22C55E),
width: double.infinity, shape: RoundedRectangleBorder(
height: 68, borderRadius: BorderRadius.circular(8),
color: AppTheme.of(context).primaryColor, ),
textStyle: AppTheme.of(context).bodyText2.override( elevation: 0,
fontFamily: AppTheme.of(context).bodyText3Family, ),
color: AppTheme.of(context).primaryBackground, child: Text(
), 'INICIAR SESIÓN',
borderRadius: BorderRadius.circular(6), style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 1.2,
),
), ),
), ),
const SizedBox(height: 68), ),
Row(
mainAxisAlignment: MainAxisAlignment.center, const SizedBox(height: 12),
children: [
Text( // Enlace "Conexión insegura"
'No registrasdo aun? ', Center(
style: AppTheme.of(context).bodyText2.override( child: TextButton(
fontFamily: AppTheme.of(context).bodyText2Family, onPressed: () {},
color: AppTheme.of(context).alternate, 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 SizedBox(height: 40),
const Icon(
Icons.copyright, // Características principales
size: 16, Text(
color: Color(0xFF99B2C6), 'CARACTERÍSTICAS PRINCIPALES',
), style: GoogleFonts.inter(
const SizedBox(width: 12), fontSize: 12,
Text( fontWeight: FontWeight.w600,
'Copyright CB Luna 2024', color: Colors.white,
style: AppTheme.of(context).bodyText3.override( letterSpacing: 1.2,
fontFamily: AppTheme.of(context).bodyText3Family,
color: AppTheme.of(context).alternate,
),
),
],
), ),
], ),
), 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, Widget _buildFeatureItem(String text) {
{bool isPassword = false}) { return Padding(
Widget? suffixIcon; padding: const EdgeInsets.only(bottom: 8),
if (isPassword) { child: Row(
suffixIcon = InkWell( crossAxisAlignment: CrossAxisAlignment.start,
onTap: () => setState( children: [
() => passwordVisibility = !passwordVisibility, Container(
), margin: const EdgeInsets.only(top: 6, right: 12),
focusNode: FocusNode(skipTraversal: true), width: 6,
child: Icon( height: 6,
passwordVisibility decoration: const BoxDecoration(
? Icons.visibility_outlined color: Color(0xFF22C55E),
: Icons.visibility_off_outlined, shape: BoxShape.circle,
color: const Color(0xFFB8B8B8), ),
size: 22, ),
), Expanded(
); child: Text(
} text,
return InputDecoration( style: GoogleFonts.inter(
hintText: label, color: Colors.white70,
filled: true, fontSize: 14,
// isDense: true, height: 1.5,
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,
), ),
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,
); );
} }
} }