feat(social): added validation for social media registration

This commit is contained in:
misha
2025-10-16 17:18:45 +03:00
parent 05bf90a3cf
commit 9e43be5b4e
3 changed files with 111 additions and 35 deletions

View File

@@ -24,7 +24,7 @@ class AppConfig {
}
final AppBuildType buildType;
late final String _baseUrl;
late String _baseUrl;
String get baseUrl => _baseUrl;
void setBaseUrl(String baseUrls) {

View File

@@ -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<OAuthState> {
}
}
Future<void> signUpViaSocialMedia({
Future<List<RegistrationValidationErrors>?> signUpViaSocialMedia({
required String provider,
required String accessToken,
required String id,
@@ -63,8 +64,38 @@ class OAuthCubit extends Cubit<OAuthState> {
);
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<String, dynamic>?;
final emailErrors = errors?['email'] as List<dynamic>?;
final phoneErrors = errors?['phone'] as List<dynamic>?;
final dateOfBirth = errors?['date_of_birth'] as List<dynamic>?;
final errorsList = <RegistrationValidationErrors>[];
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 }

View File

@@ -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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
),
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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
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<RegisterViaSocialScreen> {
),
),
),
// Показываем ошибки если есть
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<RegisterViaSocialScreen> {
);
}
void _handleRegistration(BuildContext context) {
Future<void> _handleRegistration(BuildContext context) async {
final clientID = widget.authResult?.clientId;
// Валидация полей
if (firstNameController.text.isEmpty ||
@@ -448,27 +447,45 @@ class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
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<OAuthCubit>().signUpViaSocialMedia(
final errors = await context.read<OAuthCubit>().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<RegisterViaSocialScreen> {
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('Регистрация успешна');
}
}