fix(mobile): URL changement mdp corrigée + v3.0 — multi-org, AppAuth, sécurité prod
Auth: - profile_repository.dart: /api/auth/change-password → /api/membres/auth/change-password Multi-org (Phase 3): - OrgSelectorPage, OrgSwitcherBloc, OrgSwitcherEntry - org_context_service.dart: headers X-Active-Organisation-Id + X-Active-Role Navigation: - MorePage: navigation conditionnelle par typeOrganisation - Suppression adaptive_navigation (remplacé par main_navigation_layout) Auth AppAuth: - keycloak_webview_auth_service: fixes AppAuth Android - AuthBloc: gestion REAUTH_REQUIS + premierLoginComplet Onboarding: - Nouveaux états: payment_method_page, onboarding_shared_widgets - SouscriptionStatusModel mis à jour StatutValidationSouscription Android: - build.gradle: ProGuard/R8, network_security_config - Gradle wrapper mis à jour
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/// BLoC pour la gestion des membres
|
||||
library membres_bloc;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@@ -16,6 +17,8 @@ import '../domain/usecases/delete_member.dart' as uc;
|
||||
import '../domain/usecases/search_members.dart';
|
||||
import '../domain/usecases/get_member_stats.dart';
|
||||
import '../domain/repositories/membre_repository.dart';
|
||||
import '../../../core/websocket/websocket_service.dart';
|
||||
import '../../../core/utils/logger.dart';
|
||||
|
||||
/// BLoC pour la gestion des membres (Clean Architecture)
|
||||
@injectable
|
||||
@@ -27,7 +30,10 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
final uc.DeleteMember _deleteMember;
|
||||
final SearchMembers _searchMembers;
|
||||
final GetMemberStats _getMemberStats;
|
||||
final IMembreRepository _repository; // Pour méthodes non-couvertes par use cases
|
||||
final IMembreRepository _repository;
|
||||
final WebSocketService _webSocketService;
|
||||
|
||||
StreamSubscription<WebSocketEvent>? _webSocketSubscription;
|
||||
|
||||
MembresBloc(
|
||||
this._getMembers,
|
||||
@@ -38,6 +44,7 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
this._searchMembers,
|
||||
this._getMemberStats,
|
||||
this._repository,
|
||||
this._webSocketService,
|
||||
) : super(const MembresInitial()) {
|
||||
on<LoadMembres>(_onLoadMembres);
|
||||
on<LoadMembreById>(_onLoadMembreById);
|
||||
@@ -50,6 +57,44 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
on<LoadActiveMembres>(_onLoadActiveMembres);
|
||||
on<LoadBureauMembres>(_onLoadBureauMembres);
|
||||
on<LoadMembresStats>(_onLoadMembresStats);
|
||||
on<ResetMotDePasse>(_onResetMotDePasse);
|
||||
on<AffecterOrganisation>(_onAffecterOrganisation);
|
||||
on<InviterMembre>(_onInviterMembre);
|
||||
on<ActiverAdhesion>(_onActiverAdhesion);
|
||||
on<SuspendrAdhesion>(_onSuspendrAdhesion);
|
||||
on<RadierAdhesion>(_onRadierAdhesion);
|
||||
|
||||
_initWebSocketListener();
|
||||
}
|
||||
|
||||
void _initWebSocketListener() {
|
||||
_webSocketSubscription = _webSocketService.eventStream.listen(
|
||||
(event) {
|
||||
try {
|
||||
if (event is MemberEvent) {
|
||||
AppLogger.info('MembresBloc: MemberEvent reçu (${event.eventType}), refresh liste');
|
||||
final currentState = state;
|
||||
if (currentState is MembresLoaded && !isClosed) {
|
||||
add(LoadMembres(
|
||||
refresh: true,
|
||||
organisationId: currentState.organisationId,
|
||||
page: currentState.currentPage,
|
||||
size: currentState.pageSize,
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
AppLogger.error('MembresBloc: erreur lors du traitement WebSocket event', error: e);
|
||||
}
|
||||
},
|
||||
onError: (error) => AppLogger.error('MembresBloc: WebSocket stream error', error: error),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_webSocketSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
/// Charge la liste des membres
|
||||
@@ -68,11 +113,14 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
|
||||
final MembreSearchResult result;
|
||||
if (event.organisationId != null) {
|
||||
// OrgAdmin : scope la requête à son organisation via la recherche avancée
|
||||
// OrgAdmin : scope la requête à son organisation via la recherche avancée.
|
||||
// includeInactifs=true pour récupérer aussi les membres "En attente"
|
||||
// (actif=false) — le filtrage par statut est géré côté UI.
|
||||
result = await _searchMembers(
|
||||
criteria: MembreSearchCriteria(
|
||||
organisationIds: [event.organisationId!],
|
||||
query: event.recherche?.isNotEmpty == true ? event.recherche : null,
|
||||
includeInactifs: true,
|
||||
),
|
||||
page: event.page,
|
||||
size: event.size,
|
||||
@@ -298,6 +346,60 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Réinitialise le mot de passe d'un membre
|
||||
Future<void> _onResetMotDePasse(
|
||||
ResetMotDePasse event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
|
||||
final membre = await _repository.resetMotDePasse(event.id);
|
||||
|
||||
emit(MotDePasseReinitialise(membre));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresError(
|
||||
message: 'Erreur lors de la réinitialisation du mot de passe. Veuillez réessayer.',
|
||||
error: e,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Affecte un membre à une organisation (superadmin)
|
||||
Future<void> _onAffecterOrganisation(
|
||||
AffecterOrganisation event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
|
||||
final membre = await _repository.affecterOrganisation(event.membreId, event.organisationId);
|
||||
|
||||
emit(MembreAffecte(membre));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresError(
|
||||
message: 'Erreur lors de l\'affectation à l\'organisation. Veuillez réessayer.',
|
||||
error: e,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche avancée de membres
|
||||
Future<void> _onSearchMembres(
|
||||
SearchMembres event,
|
||||
@@ -479,5 +581,116 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
return 'Erreur réseau inattendue.';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Handlers cycle de vie des adhésions ──────────────────────────────────
|
||||
|
||||
Future<void> _onInviterMembre(
|
||||
InviterMembre event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
final result = await _repository.inviterMembre(
|
||||
event.membreId,
|
||||
event.organisationId,
|
||||
roleOrg: event.roleOrg,
|
||||
);
|
||||
emit(MembreInvite(
|
||||
membreId: event.membreId,
|
||||
organisationId: event.organisationId,
|
||||
nouveauStatut: result['statut']?.toString() ?? 'INVITE',
|
||||
));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(MembresError(message: 'Erreur lors de l\'invitation du membre.', error: e));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onActiverAdhesion(
|
||||
ActiverAdhesion event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
final result = await _repository.activerAdhesion(
|
||||
event.membreId,
|
||||
event.organisationId,
|
||||
motif: event.motif,
|
||||
);
|
||||
emit(AdhesionActivee(
|
||||
membreId: event.membreId,
|
||||
nouveauStatut: result['statut']?.toString() ?? 'ACTIF',
|
||||
));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(MembresError(message: 'Erreur lors de l\'activation de l\'adhésion.', error: e));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSuspendrAdhesion(
|
||||
SuspendrAdhesion event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
final result = await _repository.suspendrAdhesion(
|
||||
event.membreId,
|
||||
event.organisationId,
|
||||
motif: event.motif,
|
||||
);
|
||||
emit(AdhesionSuspendue(
|
||||
membreId: event.membreId,
|
||||
nouveauStatut: result['statut']?.toString() ?? 'SUSPENDU',
|
||||
));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(MembresError(message: 'Erreur lors de la suspension de l\'adhésion.', error: e));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRadierAdhesion(
|
||||
RadierAdhesion event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const MembresLoading());
|
||||
final result = await _repository.radierAdhesion(
|
||||
event.membreId,
|
||||
event.organisationId,
|
||||
motif: event.motif,
|
||||
);
|
||||
emit(MembreRadie(
|
||||
membreId: event.membreId,
|
||||
nouveauStatut: result['statut']?.toString() ?? 'RADIE',
|
||||
));
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) return;
|
||||
emit(MembresNetworkError(
|
||||
message: _getNetworkErrorMessage(e),
|
||||
code: e.response?.statusCode.toString(),
|
||||
error: e,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(MembresError(message: 'Erreur lors de la radiation du membre.', error: e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user