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:
@@ -2,9 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../bloc/onboarding_bloc.dart';
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
|
||||
import '../../../../shared/design_system/tokens/unionflow_colors.dart';
|
||||
import 'plan_selection_page.dart';
|
||||
import 'period_selection_page.dart';
|
||||
import 'subscription_summary_page.dart';
|
||||
import 'payment_method_page.dart';
|
||||
import 'wave_payment_page.dart';
|
||||
import 'awaiting_validation_page.dart';
|
||||
|
||||
@@ -14,11 +17,13 @@ class OnboardingFlowPage extends StatelessWidget {
|
||||
final String onboardingState;
|
||||
final String? souscriptionId;
|
||||
final String organisationId;
|
||||
final String? typeOrganisation;
|
||||
|
||||
const OnboardingFlowPage({
|
||||
super.key,
|
||||
required this.onboardingState,
|
||||
required this.organisationId,
|
||||
this.typeOrganisation,
|
||||
this.souscriptionId,
|
||||
});
|
||||
|
||||
@@ -29,6 +34,8 @@ class OnboardingFlowPage extends StatelessWidget {
|
||||
..add(OnboardingStarted(
|
||||
initialState: onboardingState,
|
||||
existingSouscriptionId: souscriptionId,
|
||||
typeOrganisation: typeOrganisation,
|
||||
organisationId: organisationId.isNotEmpty ? organisationId : null,
|
||||
)),
|
||||
child: const _OnboardingFlowView(),
|
||||
);
|
||||
@@ -40,30 +47,76 @@ class _OnboardingFlowView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<OnboardingBloc, OnboardingState>(
|
||||
return BlocConsumer<OnboardingBloc, OnboardingState>(
|
||||
listener: (context, state) {
|
||||
// Paiement confirmé → re-check du statut (auto-activation backend)
|
||||
if (state is OnboardingPaiementConfirme) {
|
||||
context.read<AuthBloc>().add(const AuthStatusChecked());
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is OnboardingLoading || state is OnboardingInitial) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
if (state is OnboardingLoading || state is OnboardingInitial || state is OnboardingPaiementConfirme) {
|
||||
return Scaffold(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
state is OnboardingPaiementConfirme
|
||||
? 'Activation de votre compte…'
|
||||
: 'Chargement…',
|
||||
style: const TextStyle(
|
||||
color: UnionFlowColors.textSecondary,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is OnboardingError) {
|
||||
return Scaffold(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 64, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(state.message, textAlign: TextAlign.center),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.errorPale,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.error_outline,
|
||||
size: 40, color: UnionFlowColors.error),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
state.message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: UnionFlowColors.textPrimary, fontSize: 15),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () => context.read<OnboardingBloc>().add(
|
||||
OnboardingStarted(initialState: 'NO_SUBSCRIPTION'),
|
||||
const OnboardingStarted(
|
||||
initialState: 'NO_SUBSCRIPTION'),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UnionFlowColors.unionGreen,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Réessayer'),
|
||||
),
|
||||
],
|
||||
@@ -89,6 +142,10 @@ class _OnboardingFlowView extends StatelessWidget {
|
||||
return SubscriptionSummaryPage(souscription: state.souscription);
|
||||
}
|
||||
|
||||
if (state is OnboardingStepChoixPaiement) {
|
||||
return PaymentMethodPage(souscription: state.souscription);
|
||||
}
|
||||
|
||||
if (state is OnboardingStepPaiement) {
|
||||
return WavePaymentPage(
|
||||
souscription: state.souscription,
|
||||
@@ -104,7 +161,9 @@ class _OnboardingFlowView extends StatelessWidget {
|
||||
return _RejectedPage(commentaire: state.commentaire);
|
||||
}
|
||||
|
||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -117,28 +176,93 @@ class _RejectedPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
backgroundColor: UnionFlowColors.background,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.cancel_outlined, size: 72, color: Colors.red),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Souscription rejetée',
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.errorPale,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.cancel_outlined,
|
||||
size: 52, color: UnionFlowColors.error),
|
||||
),
|
||||
if (commentaire != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(commentaire!, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 28),
|
||||
const Text(
|
||||
'Demande rejetée',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Votre demande de souscription a été refusée.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: UnionFlowColors.textSecondary),
|
||||
),
|
||||
if (commentaire != null && commentaire!.isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.errorPale,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: UnionFlowColors.error.withOpacity(0.3)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.comment_outlined,
|
||||
color: UnionFlowColors.error, size: 18),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
commentaire!,
|
||||
style: const TextStyle(
|
||||
color: UnionFlowColors.textPrimary,
|
||||
fontSize: 14,
|
||||
height: 1.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () => context.read<OnboardingBloc>().add(
|
||||
OnboardingStarted(initialState: 'NO_SUBSCRIPTION'),
|
||||
),
|
||||
child: const Text('Nouvelle souscription'),
|
||||
const SizedBox(height: 36),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => context.read<OnboardingBloc>().add(
|
||||
const OnboardingStarted(
|
||||
initialState: 'NO_SUBSCRIPTION'),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UnionFlowColors.unionGreen,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: const Text('Soumettre une nouvelle demande',
|
||||
style: TextStyle(fontSize: 15)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
context.read<AuthBloc>().add(const AuthLogoutRequested()),
|
||||
child: const Text('Se déconnecter',
|
||||
style:
|
||||
TextStyle(color: UnionFlowColors.textSecondary)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user