diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..acc8b9a Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/logo_nh.png b/assets/images/logo_nh.png new file mode 100644 index 0000000..3921a07 Binary files /dev/null and b/assets/images/logo_nh.png differ diff --git a/assets/images/logo_nh_b.png b/assets/images/logo_nh_b.png new file mode 100644 index 0000000..a65a3f1 Binary files /dev/null and b/assets/images/logo_nh_b.png differ diff --git a/assets/referencia/loginpage.png b/assets/referencia/loginpage.png new file mode 100644 index 0000000..7f919a8 Binary files /dev/null and b/assets/referencia/loginpage.png differ diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart index 4975a58..c38fc7b 100644 --- a/lib/pages/login_page/login_page.dart +++ b/lib/pages/login_page/login_page.dart @@ -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 { @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(), - ) - ], - ), + ), + ], ); } }, diff --git a/lib/pages/login_page/widgets/login_form.dart b/lib/pages/login_page/widgets/login_form.dart index 3d0d46f..5b2adbf 100644 --- a/lib/pages/login_page/widgets/login_form.dart +++ b/lib/pages/login_page/widgets/login_form.dart @@ -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 { @override Widget build(BuildContext context) { final UserState userState = Provider.of(context); - double height = MediaQuery.of(context).size.height / 1024; - double width = MediaQuery.of(context).size.width / 1440; Future login() async { //Login @@ -101,257 +98,284 @@ class _LoginFormState extends State { } } - 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, ); } }