diff --git a/lib/core/app_config.dart b/lib/core/app_config.dart index 45ad250b..7dc49aa0 100644 --- a/lib/core/app_config.dart +++ b/lib/core/app_config.dart @@ -24,7 +24,7 @@ class AppConfig { } final AppBuildType buildType; - late final String _baseUrl; + late String _baseUrl; String get baseUrl => _baseUrl; void setBaseUrl(String baseUrls) { diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart index 394abc4d..52a0e73b 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart'; import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; @@ -29,7 +30,7 @@ class OAuthCubit extends Cubit { } } - Future signUpViaSocialMedia({ + Future?> signUpViaSocialMedia({ required String provider, required String accessToken, required String id, @@ -63,8 +64,38 @@ class OAuthCubit extends Cubit { ); TokenStorage.saveTokenInStorage(token); emit(OAuthState.loaded(token: token)); - } catch (e) { + } on DioException catch (e) { emit(OAuthState.error(errorMessage: e.toString())); + final errors = e.response?.data['errors'] as Map?; + final emailErrors = errors?['email'] as List?; + final phoneErrors = errors?['phone'] as List?; + final dateOfBirth = errors?['date_of_birth'] as List?; + final errorsList = []; + if (emailErrors != null && emailErrors.isNotEmpty) { + if (emailErrors.contains('Некорректный формат email')) { + errorsList.add(RegistrationValidationErrors.incorrectEmailFormat); + } + if (emailErrors.contains('Такой Эл. почта уже существует.')) { + errorsList.add(RegistrationValidationErrors.emailExist); + } + } + if (phoneErrors != null && phoneErrors.isNotEmpty) { + if (phoneErrors.contains('Такой phone уже существует.')) { + errorsList.add(RegistrationValidationErrors.phoneExist); + } + } + if (dateOfBirth != null && dateOfBirth.isNotEmpty) { + if (dateOfBirth.contains('Некорректный формат даты рождения')) { + errorsList.add(RegistrationValidationErrors.incorrectDateOfBirthFormat); + } + } + if (errorsList.isNotEmpty) { + return errorsList; + } } + + return null; } } + +enum RegistrationValidationErrors { incorrectEmailFormat, emailExist, phoneExist, incorrectDateOfBirthFormat } diff --git a/lib/features/auth/presentation/pages/register_via_social_screen.dart b/lib/features/auth/presentation/pages/register_via_social_screen.dart index 84a6f2ae..1791e24e 100644 --- a/lib/features/auth/presentation/pages/register_via_social_screen.dart +++ b/lib/features/auth/presentation/pages/register_via_social_screen.dart @@ -11,6 +11,7 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/core/firebase_api.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; @@ -42,6 +43,12 @@ class _RegisterViaSocialScreenState extends State { final dateOfBirthController = TextEditingController(); final weightController = TextEditingController(); final heightController = TextEditingController(); + bool emailExistError = false; + bool phoneExistError = false; + bool dateOfBirthError = false; + bool weightError = false; + bool heightError = false; + bool incorrectEmailFormatError = false; // Состояние для выбора пола bool isMaleSelected = true; @@ -211,8 +218,11 @@ class _RegisterViaSocialScreenState extends State { keyboardType: TextInputType.emailAddress, hintText: 'Введите email', textInputAction: TextInputAction.next, + hasError: emailExistError || incorrectEmailFormatError, onChanged: (newValue) {}, ), + if (emailExistError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Такая почта уже существует.')], + if (incorrectEmailFormatError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Некорректный формат email')], const SizedBox(height: 12), const Text('Номер телефона'), const SizedBox(height: 4), @@ -222,13 +232,13 @@ class _RegisterViaSocialScreenState extends State { controller: phoneController, keyboardType: TextInputType.number, inputFormatters: [maskFormatter], - onChanged: (newValue) { - // Дополнительная обработка изменений номера телефона - // Это поможет маске корректно работать при редактировании - }, + hasError: phoneExistError, + onChanged: (newValue) {}, ), + if (phoneExistError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Такой номер телефона уже существует.')], const SizedBox(height: 12), Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: MediaQuery.of(context).size.width / 2 - 22, @@ -243,9 +253,11 @@ class _RegisterViaSocialScreenState extends State { hintText: '01.01.2000', controller: dateOfBirthController, keyboardType: TextInputType.number, + hasError: dateOfBirthError, inputFormatters: [maskFormatterForBirthday], onChanged: (newValue) {}, ), + if (dateOfBirthError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Некорректный формат даты рождения')], ], ), ), @@ -271,6 +283,7 @@ class _RegisterViaSocialScreenState extends State { ), const SizedBox(height: 12), Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: MediaQuery.of(context).size.width / 2 - 22, @@ -280,7 +293,8 @@ class _RegisterViaSocialScreenState extends State { children: [ const Text('Вес'), const SizedBox(height: 4), - CustomTextFormField(controller: weightController, hintText: 'Введите вес', onChanged: (newValue) {}), + CustomTextFormField(controller: weightController, hintText: 'Введите вес', onChanged: (newValue) {}, hasError: weightError), + if (weightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Вес должен быть от 30 до 300 кг')], ], ), ), @@ -292,7 +306,8 @@ class _RegisterViaSocialScreenState extends State { children: [ const Text('Рост'), const SizedBox(height: 4), - CustomTextFormField(controller: heightController, hintText: 'Введите рост', onChanged: (newValue) {}), + CustomTextFormField(controller: heightController, hintText: 'Введите рост', onChanged: (newValue) {}, hasError: heightError), + if (heightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Рост должен быть от 100 до 250 см')], ], ), ), @@ -361,23 +376,7 @@ class _RegisterViaSocialScreenState extends State { ), ), ), - // Показываем ошибки если есть - if (state is OAuthStateError) ...[ - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.red.shade50, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.red.shade200), - ), - child: Text( - state.errorMessage, - style: TextStyle(color: Colors.red.shade700), - textAlign: TextAlign.center, - ), - ), - ], + // Обработка успешной регистрации if (state is OAuthStateLoaded) ...[ const SizedBox(height: 16), @@ -438,7 +437,7 @@ class _RegisterViaSocialScreenState extends State { ); } - void _handleRegistration(BuildContext context) { + Future _handleRegistration(BuildContext context) async { final clientID = widget.authResult?.clientId; // Валидация полей if (firstNameController.text.isEmpty || @@ -448,27 +447,45 @@ class _RegisterViaSocialScreenState extends State { dateOfBirthController.text.isEmpty || weightController.text.isEmpty || heightController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Пожалуйста, заполните все поля'))); + showErrorToast('Пожалуйста, заполните все поля'); return; } // Парсим числовые значения int? weight; int? height; - try { - weight = int.parse(weightController.text); - height = int.parse(heightController.text); - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Вес и рост должны быть числами'))); - return; + + weight = int.tryParse(weightController.text); + height = int.tryParse(heightController.text); + bool hasErrorWeightOrHeightError = false; + if (weight == null || height == null) { + setState(() { + weightError = true; + heightError = true; + }); + hasErrorWeightOrHeightError = true; } + if (weight! < 30 || weight > 300) { + setState(() { + weightError = true; + }); + hasErrorWeightOrHeightError = true; + } + if (height! < 100 || height > 250) { + setState(() { + heightError = true; + }); + hasErrorWeightOrHeightError = true; + } + + if (hasErrorWeightOrHeightError) return; // Очищаем номер телефона от маски для отправки final cleanPhone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), ''); if (clientID == null) return; // Вызываем OAuth регистрацию - context.read().signUpViaSocialMedia( + final errors = await context.read().signUpViaSocialMedia( provider: widget.provider ?? 'social', accessToken: widget.authResult?.accessToken ?? 'dummy_token', id: widget.authResult?.userId ?? 'dummy_id', @@ -483,5 +500,33 @@ class _RegisterViaSocialScreenState extends State { height: height, clientId: clientID, ); + if (errors != null) { + for (final error in errors) { + switch (error) { + case RegistrationValidationErrors.incorrectEmailFormat: + setState(() { + incorrectEmailFormatError = true; + }); + break; + case RegistrationValidationErrors.emailExist: + setState(() { + emailExistError = true; + }); + break; + case RegistrationValidationErrors.phoneExist: + setState(() { + phoneExistError = true; + }); + break; + case RegistrationValidationErrors.incorrectDateOfBirthFormat: + setState(() { + dateOfBirthError = true; + }); + break; + } + } + return; + } + showSuccessToast('Регистрация успешна'); } }