feat(verify): phone + email

This commit is contained in:
mso7nueh
2025-05-22 18:05:32 +03:00
parent 067e0929ed
commit 8dcfa6ee6a
11 changed files with 127 additions and 12 deletions

View File

@@ -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()),
],
),

View File

@@ -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<String, dynamic> && data['type'] == 'unverificated_phone') {
final String phone = await sl<ProfileRepository>().getMyPhone();
final String passKey = await sl<ProfileRepository>().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);
}
}

View File

@@ -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<void> 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());
}

View File

@@ -119,7 +119,7 @@ class _AuthScreenState extends State<AuthScreen> {
children: [
const Text('Нет аккаунта? '),
GestureDetector(
onTap: () => GoRouter.of(context).goNamed('webViewRegistrationScreen'),
onTap: () => GoRouter.of(context).goNamed('registrationScreen'),
child: Text(
'Зарегистрироваться',
style: TextStyle(

View File

@@ -36,10 +36,6 @@ class ProfileCubit extends Cubit<ProfileState> {
}
}
Future<String> getPassKey(String phone) async {
return (await profileRepository.getPassKey('+79150626151'));
}
Future<void> getMyProfile({
String period = '1',
}) async {

View File

@@ -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<ProfileScreen> {
final List<String> chipsLabelList = ['Вес', 'Замеры', 'ИМТ'];
final List<String> chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ'];
final List<String> chipsIntervalList = ['1', '3', '6', '12', 'all'];
final TextEditingController controller = TextEditingController();
List<WeightModel> weightPoints = [];
List<CircumferenceModel> circumferencePoints = [];
@@ -222,9 +224,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
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<ProfileRepository>().sendEmailCode();
}, child: Text('send email verify code')),
TextFormField(
controller: controller,
),
ElevatedButton(onPressed: () async {
await sl<ProfileRepository>().tryVerifyEmailByCode(controller.text);
}, child: Text('try verify email by code')),
_header(state),
if (state.profile.roles.contains('consultant'))
Column(

View File

@@ -185,4 +185,16 @@ class ProfileRepository {
Future<String> getPassKey(String phone) async {
return (jsonDecode((await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!;
}
Future<String> getMyPhone() async {
return (jsonDecode(await profileService.getMyPhone())['phone']);
}
Future<void> sendEmailCode() async {
return await profileService.sendEmailCode();
}
Future<void> tryVerifyEmailByCode(String code) async {
return await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}");
}
}

View File

@@ -108,4 +108,15 @@ abstract class ProfileService {
Future<String> getPassKey({
@Body() required String json,
});
@GET('/api/profile/my_phone')
Future<String> getMyPhone();
@POST('/api/profile/send_email_code')
Future<void> sendEmailCode();
@POST('/api/profile/verificate_email_code')
Future<void> tryVerifyEmailByCode({
@Body() required String code,
});
}

View File

@@ -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,

View File

@@ -25,7 +25,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
birthdate: '',
gender: '',
location: '',
responseBody: '',
responseBody: '{}',
lang: 'Русский',
weight: 0,
height: 0,
@@ -54,6 +54,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
final token = await registrationRepository.tryRegister(
state.sheet.copyWith(
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
responseBody: '{}',
),
);
await TokenStorage.saveTokenInStorage(token);

View File

@@ -478,4 +478,49 @@ class MockProfileService extends _i1.Mock implements _i5.ProfileService {
),
)),
) as _i6.Future<String>);
@override
_i6.Future<String> getMyPhone() => (super.noSuchMethod(
Invocation.method(
#getMyPhone,
[],
),
returnValue: _i6.Future<String>.value(_i11.dummyValue<String>(
this,
Invocation.method(
#getMyPhone,
[],
),
)),
returnValueForMissingStub:
_i6.Future<String>.value(_i11.dummyValue<String>(
this,
Invocation.method(
#getMyPhone,
[],
),
)),
) as _i6.Future<String>);
@override
_i6.Future<void> sendEmailCode() => (super.noSuchMethod(
Invocation.method(
#sendEmailCode,
[],
),
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i6.Future<void> tryVerifyEmailByCode({required String? code}) =>
(super.noSuchMethod(
Invocation.method(
#tryVerifyEmailByCode,
[],
{#code: code},
),
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
}