feat(core_localization): add error translation utility and new error messages

feat(client_auth): implement error handling with localized messages
feat(client_hubs): implement error handling with localized messages
feat(client_billing): navigate to home after billing
feat(client_coverage): navigate to home after coverage
feat(client_create_order): navigate to home after create order
feat(client_settings): navigate to home after settings
feat(client_view_orders): show hub name in order card
fix(client_auth): handle existing firebase accounts during sign-up

This commit introduces a new utility function, `translateErrorKey`,
to translate error message keys to localized strings. It also adds
new error messages to the localization files for both English and
Spanish.

The commit also implements error handling with localized messages in
the client authentication and hubs features. This makes it easier for
users to understand what went wrong and how to fix it.

Additionally, the commit updates the navigation flow for the billing,
coverage, create order, and settings features to navigate to the home
page after the user completes the action.

Finally, the commit fixes a bug where the hub name was not being
displayed in the order card.
This commit is contained in:
bwnyasse
2026-01-31 18:56:48 -05:00
parent 9517606e7a
commit caac050ac9
30 changed files with 1396 additions and 105 deletions

View File

@@ -5,6 +5,12 @@ import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:http/http.dart' as http;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart' as domain;
import 'package:krow_domain/krow_domain.dart'
show
HubHasOrdersException,
HubCreationFailedException,
BusinessNotFoundException,
NotAuthenticatedException;
import '../../domain/repositories/hub_repository_interface.dart';
import '../../util/hubs_constants.dart';
@@ -67,7 +73,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
.execute();
final String? createdId = result.data?.teamHub_insert.id;
if (createdId == null) {
throw Exception('Hub creation failed.');
throw HubCreationFailedException(
technicalMessage: 'teamHub_insert returned null for hub: $name',
);
}
final List<domain.Hub> hubs = await _fetchHubsForTeam(
@@ -97,7 +105,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
await _firebaseAuth.signOut();
throw Exception('Business is missing. Please sign in again.');
throw const BusinessNotFoundException(
technicalMessage: 'Business ID missing from session',
);
}
final QueryResult<
@@ -110,7 +120,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
.execute();
if (result.data.orders.isNotEmpty) {
throw Exception("Sorry this hub has orders, it can't be deleted.");
throw HubHasOrdersException(
technicalMessage: 'Hub $id has ${result.data.orders.length} orders',
);
}
await _dataConnect.deleteTeamHub(id: id).execute();
@@ -151,7 +163,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
final firebase.User? user = _firebaseAuth.currentUser;
if (user == null) {
throw Exception('User is not authenticated.');
throw const NotAuthenticatedException(
technicalMessage: 'No Firebase user in currentUser',
);
}
final QueryResult<dc.GetBusinessesByUserIdData, dc.GetBusinessesByUserIdVariables> result = await _dataConnect.getBusinessesByUserId(
@@ -159,7 +173,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
).execute();
if (result.data.businesses.isEmpty) {
await _firebaseAuth.signOut();
throw Exception('No business found for this user. Please sign in again.');
throw BusinessNotFoundException(
technicalMessage: 'No business found for user ${user.uid}',
);
}
final dc.GetBusinessesByUserIdBusinesses business = result.data.businesses.first;
@@ -206,7 +222,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables> createTeamResult = await createTeamBuilder.execute();
final String? teamId = createTeamResult.data?.team_insert.id;
if (teamId == null) {
throw Exception('Team creation failed.');
throw HubCreationFailedException(
technicalMessage: 'Team creation failed for business ${business.id}',
);
}
return teamId;

View File

@@ -1,3 +1,5 @@
import 'dart:developer' as developer;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
@@ -67,11 +69,20 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
try {
final List<Hub> hubs = await _getHubsUseCase();
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
} catch (e) {
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.failure,
errorMessage: e.toString(),
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.failure,
errorMessage: 'errors.generic.unknown',
),
);
}
@@ -106,11 +117,20 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
showAddHubDialog: false,
),
);
} catch (e) {
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.toString(),
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
@@ -131,11 +151,20 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
successMessage: 'Hub deleted successfully',
),
);
} catch (e) {
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.toString(),
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
@@ -159,11 +188,20 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
clearHubToIdentify: true,
),
);
} catch (e) {
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.toString(),
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
@@ -175,8 +213,8 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
) {
emit(
state.copyWith(
errorMessage: null,
successMessage: null,
clearErrorMessage: true,
clearSuccessMessage: true,
status:
state.status == ClientHubsStatus.actionSuccess ||
state.status == ClientHubsStatus.actionFailure

View File

@@ -43,12 +43,18 @@ class ClientHubsState extends Equatable {
bool? showAddHubDialog,
Hub? hubToIdentify,
bool clearHubToIdentify = false,
bool clearErrorMessage = false,
bool clearSuccessMessage = false,
}) {
return ClientHubsState(
status: status ?? this.status,
hubs: hubs ?? this.hubs,
errorMessage: errorMessage ?? this.errorMessage,
successMessage: successMessage ?? this.successMessage,
errorMessage: clearErrorMessage
? null
: (errorMessage ?? this.errorMessage),
successMessage: clearSuccessMessage
? null
: (successMessage ?? this.successMessage),
showAddHubDialog: showAddHubDialog ?? this.showAddHubDialog,
hubToIdentify: clearHubToIdentify
? null

View File

@@ -33,9 +33,10 @@ class ClientHubsPage extends StatelessWidget {
},
listener: (BuildContext context, ClientHubsState state) {
if (state.errorMessage != null && state.errorMessage!.isNotEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(state.errorMessage!)));
final String errorMessage = translateErrorKey(state.errorMessage!);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errorMessage)),
);
BlocProvider.of<ClientHubsBloc>(
context,
).add(const ClientHubsMessageCleared());
@@ -178,7 +179,7 @@ class ClientHubsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GestureDetector(
onTap: () => Modular.to.pop(),
onTap: () => Modular.to.navigate('/client-main/home/'),
child: Container(
width: 40,
height: 40,