From 8dcfa6ee6abb8b0cfe9217ffb3a376930c59364e Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Thu, 22 May 2025 18:05:32 +0300 Subject: [PATCH] feat(verify): phone + email --- lib/core/app_router.dart | 2 +- .../dio/phone_verify_error_interceptor.dart | 29 ++++++++++++ lib/core/locator_service.dart | 16 ++++++- .../auth/presentation/pages/auth_screen.dart | 2 +- .../bloc/profile_cubit/profile_cubit.dart | 4 -- .../presentation/pages/profile_screen.dart | 13 ++++-- .../repositories/profile_repository.dart | 12 +++++ .../profile/services/profile_service.dart | 11 +++++ .../models/user_registration_model.dart | 2 +- .../registration_cubit.dart | 3 +- .../services/profile_service_test.mocks.dart | 45 +++++++++++++++++++ 11 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 lib/core/dio/phone_verify_error_interceptor.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 83030559..bd3688a6 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -77,7 +77,7 @@ class AppRouter { name: 'authScreen', builder: (context, state) => const AuthScreen(), routes: [ - GoRoute(path: 'webView-registration-screen', name: 'webViewRegistrationScreen', builder: (context, state) => const WebViewRegistrationScreen()), + // GoRoute(path: 'webView-registration-screen', name: 'webViewRegistrationScreen', builder: (context, state) => const WebViewRegistrationScreen()), GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()), ], ), diff --git a/lib/core/dio/phone_verify_error_interceptor.dart b/lib/core/dio/phone_verify_error_interceptor.dart new file mode 100644 index 00000000..5d764cff --- /dev/null +++ b/lib/core/dio/phone_verify_error_interceptor.dart @@ -0,0 +1,29 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:go_router/go_router.dart'; + +class PhoneVerifyErrorInterceptor extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + if (err.response?.statusCode == 405) { + final data = err.response?.data; + + if (data is Map && data['type'] == 'unverificated_phone') { + final String phone = await sl().getMyPhone(); + final String passKey = await sl().getPassKey(phone); + AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey); + return handler.reject( + DioException( + requestOptions: err.requestOptions, + response: err.response, + error: 'Phone number not verified', + type: DioExceptionType.badResponse, + ), + ); + } + } + return handler.next(err); + } +} diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 15bb5270..fb5b82fa 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart'; import 'package:eatvio_app/core/interceptors/token_interceptor.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/core/websockets/ws_service.dart'; @@ -191,7 +192,20 @@ Future init() async { // External sl.registerLazySingleton( - () => Dio(BaseOptions(baseUrl: baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))..interceptors.add(TokenInterceptor()), + () => Dio( + BaseOptions( + baseUrl: baseUrl, + headers: { + 'accept': 'application/json', + 'content-type': 'application/json', + }, + ), + )..interceptors.addAll( + [ + TokenInterceptor(), + PhoneVerifyErrorInterceptor(), + ], + ), ); sl.registerLazySingleton(() async => const FlutterSecureStorage()); } diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index cbd82a44..8c068755 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -119,7 +119,7 @@ class _AuthScreenState extends State { children: [ const Text('Нет аккаунта? '), GestureDetector( - onTap: () => GoRouter.of(context).goNamed('webViewRegistrationScreen'), + onTap: () => GoRouter.of(context).goNamed('registrationScreen'), child: Text( 'Зарегистрироваться', style: TextStyle( diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index f06b4dc8..cdbab793 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -36,10 +36,6 @@ class ProfileCubit extends Cubit { } } - Future getPassKey(String phone) async { - return (await profileRepository.getPassKey('+79150626151')); - } - Future getMyProfile({ String period = '1', }) async { diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 8dfd7eb0..f9a3961c 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -26,6 +26,7 @@ import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_po import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -54,6 +55,7 @@ class _ProfileScreenState extends State { final List chipsLabelList = ['Вес', 'Замеры', 'ИМТ']; final List chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ']; final List chipsIntervalList = ['1', '3', '6', '12', 'all']; + final TextEditingController controller = TextEditingController(); List weightPoints = []; List circumferencePoints = []; @@ -222,9 +224,14 @@ class _ProfileScreenState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ ElevatedButton(onPressed: () async { - final String passKey = await profileCubit.getPassKey('+79150626151'); - GoRouter.of(context).pushNamed('verifyScreen', extra: passKey); - }, child: Text('phone verify')), + await sl().sendEmailCode(); + }, child: Text('send email verify code')), + TextFormField( + controller: controller, + ), + ElevatedButton(onPressed: () async { + await sl().tryVerifyEmailByCode(controller.text); + }, child: Text('try verify email by code')), _header(state), if (state.profile.roles.contains('consultant')) Column( diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 9c4f5fb4..46b3bcf6 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -185,4 +185,16 @@ class ProfileRepository { Future getPassKey(String phone) async { return (jsonDecode((await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!; } + + Future getMyPhone() async { + return (jsonDecode(await profileService.getMyPhone())['phone']); + } + + Future sendEmailCode() async { + return await profileService.sendEmailCode(); + } + + Future tryVerifyEmailByCode(String code) async { + return await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}"); + } } diff --git a/lib/features/profile/services/profile_service.dart b/lib/features/profile/services/profile_service.dart index 0b1490dc..06af37b9 100644 --- a/lib/features/profile/services/profile_service.dart +++ b/lib/features/profile/services/profile_service.dart @@ -108,4 +108,15 @@ abstract class ProfileService { Future getPassKey({ @Body() required String json, }); + + @GET('/api/profile/my_phone') + Future getMyPhone(); + + @POST('/api/profile/send_email_code') + Future sendEmailCode(); + + @POST('/api/profile/verificate_email_code') + Future tryVerifyEmailByCode({ + @Body() required String code, + }); } diff --git a/lib/features/registration/models/user_registration_model.dart b/lib/features/registration/models/user_registration_model.dart index 1d75ba24..b999a077 100644 --- a/lib/features/registration/models/user_registration_model.dart +++ b/lib/features/registration/models/user_registration_model.dart @@ -16,7 +16,7 @@ abstract class UserRegistrationSheet with _$UserRegistrationSheet { required String birthdate, required String gender, required String location, - @JsonKey(name: 'response_body') required String responseBody, + @JsonKey(name: 'response_body', defaultValue: '{}') required String responseBody, required String lang, required int weight, required int height, diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index 33688c9c..7b371f22 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -25,7 +25,7 @@ class RegistrationCubit extends Cubit { birthdate: '', gender: '', location: '', - responseBody: '', + responseBody: '{}', lang: 'Русский', weight: 0, height: 0, @@ -54,6 +54,7 @@ class RegistrationCubit extends Cubit { final token = await registrationRepository.tryRegister( state.sheet.copyWith( phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''), + responseBody: '{}', ), ); await TokenStorage.saveTokenInStorage(token); diff --git a/test/features/profile/services/profile_service_test.mocks.dart b/test/features/profile/services/profile_service_test.mocks.dart index b0aa976a..22c3af52 100644 --- a/test/features/profile/services/profile_service_test.mocks.dart +++ b/test/features/profile/services/profile_service_test.mocks.dart @@ -478,4 +478,49 @@ class MockProfileService extends _i1.Mock implements _i5.ProfileService { ), )), ) as _i6.Future); + + @override + _i6.Future getMyPhone() => (super.noSuchMethod( + Invocation.method( + #getMyPhone, + [], + ), + returnValue: _i6.Future.value(_i11.dummyValue( + this, + Invocation.method( + #getMyPhone, + [], + ), + )), + returnValueForMissingStub: + _i6.Future.value(_i11.dummyValue( + this, + Invocation.method( + #getMyPhone, + [], + ), + )), + ) as _i6.Future); + + @override + _i6.Future sendEmailCode() => (super.noSuchMethod( + Invocation.method( + #sendEmailCode, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future tryVerifyEmailByCode({required String? code}) => + (super.noSuchMethod( + Invocation.method( + #tryVerifyEmailByCode, + [], + {#code: code}, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); }