From 95d0e4502e3f357f40af322384ac499fe041a02b Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:35:32 +0000 Subject: [PATCH] Refactoring - Version stable --- .../dashboard_remote_datasource.dart | 6 ++ .../dashboard_repository_impl.dart | 46 ++++++++--- .../presentation/bloc/dashboard_bloc.dart | 24 ++++-- .../presentation/bloc/dashboard_state.dart | 6 ++ .../pages/connected_dashboard_page.dart | 76 +++++++++++++++++++ 5 files changed, 141 insertions(+), 17 deletions(-) diff --git a/lib/features/dashboard/data/datasources/dashboard_remote_datasource.dart b/lib/features/dashboard/data/datasources/dashboard_remote_datasource.dart index 8f50b7d..8323b70 100644 --- a/lib/features/dashboard/data/datasources/dashboard_remote_datasource.dart +++ b/lib/features/dashboard/data/datasources/dashboard_remote_datasource.dart @@ -64,6 +64,9 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource { throw ServerException('Failed to load member dashboard: ${response.statusCode}'); } } on DioException catch (e) { + if (e.response?.statusCode == 404) { + throw const NotFoundException('Profil membre non trouvé'); + } AppLogger.error('DashboardRemoteDataSource: getMemberDashboardData', error: e); throw ServerException('Network error: ${e.message}'); } catch (e, st) { @@ -102,6 +105,9 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource { throw ServerException('Failed to load adherent account: ${response.statusCode}'); } } on DioException catch (e) { + if (e.response?.statusCode == 404) { + throw const NotFoundException('Compte adhérent non trouvé'); + } AppLogger.error('DashboardRemoteDataSource: getCompteAdherent', error: e); throw ServerException('Network error: ${e.message}'); } catch (e, st) { diff --git a/lib/features/dashboard/data/repositories/dashboard_repository_impl.dart b/lib/features/dashboard/data/repositories/dashboard_repository_impl.dart index 33039b9..69ed22d 100644 --- a/lib/features/dashboard/data/repositories/dashboard_repository_impl.dart +++ b/lib/features/dashboard/data/repositories/dashboard_repository_impl.dart @@ -9,6 +9,7 @@ import '../models/membre_dashboard_synthese_model.dart'; import '../models/compte_adherent_model.dart'; import '../../../../core/error/exceptions.dart'; import '../../../../core/error/failures.dart'; +import '../../../../core/utils/logger.dart'; import '../../../../core/network/network_info.dart'; @LazySingleton(as: DashboardRepository) @@ -29,6 +30,13 @@ class DashboardRepositoryImpl implements DashboardRepository { try { final model = await remoteDataSource.getCompteAdherent(); return Right(_mapCompteToEntity(model)); + } on NotFoundException { + return const Left(NotFoundFailure( + 'Compte adhérent non trouvé', + null, + false, + 'Votre profil membre est en cours de création.', + )); } on ServerException catch (e) { return Left(ServerFailure(e.message)); } catch (e) { @@ -49,13 +57,26 @@ class DashboardRepositoryImpl implements DashboardRepository { final useMemberDashboard = organizationId.trim().isEmpty; if (useMemberDashboard) { // Chargement parallèle de la synthèse et du compte adhérent unifié - final results = await Future.wait([ - remoteDataSource.getMemberDashboardData(), - remoteDataSource.getCompteAdherent(), - ]); - - final synthese = results[0] as MembreDashboardSyntheseModel; - final compteModel = results[1] as CompteAdherentModel; + MembreDashboardSyntheseModel? synthese; + CompteAdherentModel? compteModel; + + try { + final results = await Future.wait([ + remoteDataSource.getMemberDashboardData(), + remoteDataSource.getCompteAdherent(), + ]); + synthese = results[0] as MembreDashboardSyntheseModel; + compteModel = results[1] as CompteAdherentModel; + } on NotFoundException { + // Le membre existe dans Keycloak mais pas encore en base → état intermédiaire + AppLogger.info('DashboardRepository: membre non encore enregistré en base'); + return const Left(NotFoundFailure( + 'Profil membre non trouvé', + null, + false, + 'Votre profil membre est en cours de création.', + )); + } // Fallback : si les montants sont à zéro mais qu'il y a des cotisations, // on complète avec /api/cotisations/mes-cotisations/synthese @@ -67,8 +88,8 @@ class DashboardRepositoryImpl implements DashboardRepository { } return Right(_mapMemberSyntheseToEntity( - synthese, - userId, + synthese, + userId, cotSynthese: cotSynthese, compteModel: compteModel, )); @@ -76,6 +97,13 @@ class DashboardRepositoryImpl implements DashboardRepository { final dashboardData = await remoteDataSource.getDashboardData(organizationId, userId); return Right(_mapToEntity(dashboardData)); + } on NotFoundException { + return const Left(NotFoundFailure( + 'Profil membre non trouvé', + null, + false, + 'Votre profil membre est en cours de création.', + )); } on ServerException catch (e) { return Left(ServerFailure(e.message)); } catch (e) { diff --git a/lib/features/dashboard/presentation/bloc/dashboard_bloc.dart b/lib/features/dashboard/presentation/bloc/dashboard_bloc.dart index 1bc8927..2530b19 100644 --- a/lib/features/dashboard/presentation/bloc/dashboard_bloc.dart +++ b/lib/features/dashboard/presentation/bloc/dashboard_bloc.dart @@ -94,7 +94,13 @@ class DashboardBloc extends Bloc { ); result.fold( - (failure) => emit(DashboardError(_mapFailureToMessage(failure))), + (failure) { + if (failure is NotFoundFailure) { + emit(const DashboardMemberNotRegistered()); + } else { + emit(DashboardError(_mapFailureToMessage(failure))); + } + }, (dashboardData) => emit(DashboardLoaded(dashboardData)), ); } @@ -271,14 +277,16 @@ class DashboardBloc extends Bloc { } String _mapFailureToMessage(Failure failure) { - switch (failure.runtimeType) { - case ServerFailure: - return 'Erreur serveur. Veuillez réessayer.'; - case NetworkFailure: - return 'Pas de connexion internet. Vérifiez votre connexion.'; - default: - return 'Une erreur inattendue s\'est produite.'; + if (failure is NetworkFailure) { + return 'Pas de connexion internet. Vérifiez votre connexion.'; } + if (failure is UnauthorizedFailure) { + return 'Session expirée. Veuillez vous reconnecter.'; + } + if (failure is NotFoundFailure) { + return failure.userFriendlyMessage ?? 'Ressource non trouvée.'; + } + return 'Erreur serveur. Veuillez réessayer.'; } @override diff --git a/lib/features/dashboard/presentation/bloc/dashboard_state.dart b/lib/features/dashboard/presentation/bloc/dashboard_state.dart index 4b7d458..df62049 100644 --- a/lib/features/dashboard/presentation/bloc/dashboard_state.dart +++ b/lib/features/dashboard/presentation/bloc/dashboard_state.dart @@ -37,3 +37,9 @@ class DashboardError extends DashboardState { @override List get props => [message]; } + +/// État émis quand l'utilisateur est authentifié dans Keycloak +/// mais n'a pas encore de fiche membre en base de données. +class DashboardMemberNotRegistered extends DashboardState { + const DashboardMemberNotRegistered(); +} diff --git a/lib/features/dashboard/presentation/pages/connected_dashboard_page.dart b/lib/features/dashboard/presentation/pages/connected_dashboard_page.dart index 2ea4d90..e91a872 100644 --- a/lib/features/dashboard/presentation/pages/connected_dashboard_page.dart +++ b/lib/features/dashboard/presentation/pages/connected_dashboard_page.dart @@ -60,6 +60,10 @@ class _ConnectedDashboardPageState extends State with Si ); } + if (state is DashboardMemberNotRegistered) { + return _buildMemberNotRegisteredState(); + } + if (state is DashboardError) { return _buildErrorState(state.message); } @@ -624,6 +628,78 @@ class _ConnectedDashboardPageState extends State with Si ); } + Widget _buildMemberNotRegisteredState() { + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(28), + decoration: BoxDecoration( + gradient: UnionFlowColors.primaryGradient, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.person_add_alt_1_outlined, + size: 56, + color: Colors.white, + ), + ), + const SizedBox(height: 28), + const Text( + 'Bienvenue dans UnionFlow', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w800, + color: UnionFlowColors.textPrimary, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + const Text( + 'Votre compte est en cours de configuration par un administrateur. ' + 'Votre tableau de bord sera disponible dès que votre profil membre aura été activé.', + style: TextStyle( + fontSize: 14, + color: UnionFlowColors.textSecondary, + height: 1.5, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), + decoration: BoxDecoration( + color: UnionFlowColors.unionGreen.withOpacity(0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: UnionFlowColors.unionGreen.withOpacity(0.3)), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.info_outline, size: 18, color: UnionFlowColors.unionGreen), + SizedBox(width: 10), + Flexible( + child: Text( + 'Contactez votre administrateur si ce message persiste.', + style: TextStyle( + fontSize: 13, + color: UnionFlowColors.unionGreen, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + Widget _buildErrorState(String message) { return Center( child: AnimatedFadeIn(