fix(inscription-am): étape 1/2 obligatoires alignées API (min 2 car., photo)
- Étape 1 : prénom/nom min 2 caractères (minPersonNameLength sur PersonalInfoFormScreen pour AM) - Étape 2 : ville/pays naissance min 2 ; photo réelle + consentement obligatoires ; isStep2Complete strict - registerAM : lieu naissance trim + toujours envoyer photo_base64 ; lieu_* non null si valide - Étape 4 : blocage envoi si isRegistrationComplete faux Made-with: Cursor
This commit is contained in:
@@ -55,6 +55,8 @@ class PersonalInfoFormScreen extends StatefulWidget {
|
||||
final PersonalInfoData? referenceAddressData; // Pour pré-remplir si "même adresse"
|
||||
final bool embedContentOnly; // Si true, affiche seulement la carte (sans scaffold/fond/titre)
|
||||
final VoidCallback? onEdit; // Callback pour le bouton d'édition (si affiché)
|
||||
/// Si non null (ex. 2 pour inscription AM), nom/prénom : trim + longueur minimale (aligné API).
|
||||
final int? minPersonNameLength;
|
||||
|
||||
const PersonalInfoFormScreen({
|
||||
super.key,
|
||||
@@ -72,6 +74,7 @@ class PersonalInfoFormScreen extends StatefulWidget {
|
||||
this.referenceAddressData,
|
||||
this.embedContentOnly = false,
|
||||
this.onEdit,
|
||||
this.minPersonNameLength,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -214,6 +217,16 @@ class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||||
return validateEmail(value, allowEmpty: true);
|
||||
}
|
||||
|
||||
/// Prénom / nom avec longueur minimale (ex. inscription AM, aligné `MinLength` API).
|
||||
String? _validatePersonNameWithMinLength(String? value) {
|
||||
final min = widget.minPersonNameLength;
|
||||
if (min == null) return null;
|
||||
final t = (value ?? '').trim();
|
||||
if (t.isEmpty) return 'Ce champ est obligatoire';
|
||||
if (t.length < min) return 'Au moins $min caractères';
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Téléphone / e-mail du parent 2 : pas de validation si « Ajouter Parent 2 » est désactivé.
|
||||
String? _validateFrenchPhoneIfSecondParentNeeded(String? value) {
|
||||
if (widget.showSecondPersonToggle && !_fieldsEnabled) {
|
||||
@@ -837,6 +850,9 @@ class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||||
enabled: _fieldsEnabled,
|
||||
focusOrder: _focusLastName,
|
||||
focusNode: _lastNameFocus,
|
||||
fieldValidator: widget.minPersonNameLength != null
|
||||
? _validatePersonNameWithMinLength
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
@@ -849,6 +865,9 @@ class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||||
enabled: _fieldsEnabled,
|
||||
focusOrder: _focusFirstName,
|
||||
focusNode: _firstNameFocus,
|
||||
fieldValidator: widget.minPersonNameLength != null
|
||||
? _validatePersonNameWithMinLength
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1106,6 +1125,9 @@ class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||||
enabled: _fieldsEnabled,
|
||||
focusOrder: _focusLastName,
|
||||
focusNode: _lastNameFocus,
|
||||
fieldValidator: widget.minPersonNameLength != null
|
||||
? _validatePersonNameWithMinLength
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -1118,6 +1140,9 @@ class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||||
enabled: _fieldsEnabled,
|
||||
focusOrder: _focusFirstName,
|
||||
focusNode: _firstNameFocus,
|
||||
fieldValidator: widget.minPersonNameLength != null
|
||||
? _validatePersonNameWithMinLength
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
|
||||
@@ -189,6 +189,20 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String? _validateBirthCity(String? v) {
|
||||
final t = (v ?? '').trim();
|
||||
if (t.isEmpty) return 'Ville requise';
|
||||
if (t.length < 2) return 'Au moins 2 caractères';
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validateBirthCountry(String? v) {
|
||||
final t = (v ?? '').trim();
|
||||
if (t.isEmpty) return 'Pays requis';
|
||||
if (t.length < 2) return 'Au moins 2 caractères';
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validatePlacesAvailable(String? v) {
|
||||
if (v == null || v.isEmpty) return 'Places disponibles requises';
|
||||
final n = int.tryParse(v);
|
||||
@@ -281,7 +295,13 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
_applyPlaceNameFormat(_birthCityController);
|
||||
_applyPlaceNameFormat(_birthCountryController);
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (_hasRealPhoto && !_photoConsent) {
|
||||
if (!_hasRealPhoto) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Veuillez ajouter une photo de profil.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_photoConsent) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
||||
);
|
||||
@@ -295,8 +315,8 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
photoFilename: _photoFilename,
|
||||
photoConsent: _photoConsent,
|
||||
dateOfBirth: _selectedDate,
|
||||
birthCity: _birthCityController.text,
|
||||
birthCountry: _birthCountryController.text,
|
||||
birthCity: _birthCityController.text.trim(),
|
||||
birthCountry: _birthCountryController.text.trim(),
|
||||
nir: normalizeNir(_nirController.text),
|
||||
agrementNumber: _agrementController.text,
|
||||
agreementDate: _selectedAgreementDate,
|
||||
@@ -753,7 +773,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
label: 'Ville de naissance',
|
||||
controller: _birthCityController,
|
||||
hint: 'Ex. Ajaccio, Paris, Casablanca…',
|
||||
validator: (v) => v!.isEmpty ? 'Ville requise' : null,
|
||||
validator: _validateBirthCity,
|
||||
focusNode: _birthCityFocus,
|
||||
),
|
||||
SizedBox(height: verticalSpacing),
|
||||
@@ -762,7 +782,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
label: 'Pays de naissance',
|
||||
controller: _birthCountryController,
|
||||
hint: 'Ex. France, Maroc…',
|
||||
validator: (v) => v!.isEmpty ? 'Pays requis' : null,
|
||||
validator: _validateBirthCountry,
|
||||
focusNode: _birthCountryFocus,
|
||||
),
|
||||
],
|
||||
@@ -875,7 +895,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
label: 'Ville de naissance',
|
||||
controller: _birthCityController,
|
||||
hint: 'Ex. Ajaccio, Paris, Casablanca…',
|
||||
validator: (v) => v!.isEmpty ? 'Ville requise' : null,
|
||||
validator: _validateBirthCity,
|
||||
focusNode: _birthCityFocus,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -885,7 +905,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
label: 'Pays de naissance',
|
||||
controller: _birthCountryController,
|
||||
hint: 'Ex. France, Maroc…',
|
||||
validator: (v) => v!.isEmpty ? 'Pays requis' : null,
|
||||
validator: _validateBirthCountry,
|
||||
focusNode: _birthCountryFocus,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Reference in New Issue
Block a user