[#101] [Frontend] Inscription parent — API, soumission et validation
Squash merge de develop vers master. Livrables principaux (ticket #101 et mise au point associée) : - Branchement du formulaire d'inscription parent sur POST /api/v1/auth/register/parent - Payload DTO (parents, enfants, photos base64, CGU) et services Auth - Parcours gestionnaire : cartes dossiers, wizard validation famille, images authentifiées - Scripts d'inscription test (Martin, Durand/Rousseau, Lecomte) ; .gitignore .cursor/ Inclut également les ajustements develop fusionnés dans ce lot (inscription AM, champs relais, etc.). Closes #101 Made-with: Cursor
This commit is contained in:
@@ -3,15 +3,16 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'dart:math' as math; // Pour la rotation du chevron
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io' show File;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import '../../widgets/hover_relief_widget.dart';
|
||||
import '../../widgets/child_card_widget.dart';
|
||||
import '../../widgets/custom_navigation_button.dart';
|
||||
import '../../models/user_registration_data.dart';
|
||||
import '../../utils/data_generator.dart';
|
||||
import '../../models/card_assets.dart';
|
||||
import '../../config/display_config.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:p_tits_pas/utils/name_format_utils.dart';
|
||||
|
||||
class ParentRegisterStep3Screen extends StatefulWidget {
|
||||
// final UserRegistrationData registrationData; // Supprimé
|
||||
@@ -75,6 +76,22 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Même logique que nom / prénom parent : normalisation avant passage à l’étape suivante.
|
||||
void _normalizeChildrenNamesAndGoToStep4(
|
||||
BuildContext context,
|
||||
UserRegistrationData registrationData,
|
||||
) {
|
||||
for (var i = 0; i < registrationData.children.length; i++) {
|
||||
final c = registrationData.children[i];
|
||||
final fn = formatPersonNameCase(c.firstName);
|
||||
final ln = formatPersonNameCase(c.lastName);
|
||||
if (fn != c.firstName || ln != c.lastName) {
|
||||
registrationData.updateChild(i, c.copyWith(firstName: fn, lastName: ln));
|
||||
}
|
||||
}
|
||||
context.go('/parent-register-step4');
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (!_scrollController.hasClients) return;
|
||||
final position = _scrollController.position;
|
||||
@@ -92,8 +109,6 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
|
||||
void _addChild(UserRegistrationData registrationData) { // Prend registrationData
|
||||
setState(() {
|
||||
bool isUnborn = DataGenerator.boolean();
|
||||
|
||||
// Trouver la première couleur non utilisée
|
||||
CardColorVertical cardColor = _childCardColors.firstWhere(
|
||||
(color) => !_usedColors.contains(color),
|
||||
@@ -102,11 +117,11 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
|
||||
final newChild = ChildData(
|
||||
lastName: registrationData.parent1.lastName,
|
||||
firstName: DataGenerator.firstName(),
|
||||
dob: DataGenerator.dob(isUnborn: isUnborn),
|
||||
isUnbornChild: isUnborn,
|
||||
photoConsent: DataGenerator.boolean(),
|
||||
multipleBirth: DataGenerator.boolean(),
|
||||
firstName: '',
|
||||
dob: '',
|
||||
isUnbornChild: false,
|
||||
photoConsent: false,
|
||||
multipleBirth: false,
|
||||
cardColor: cardColor,
|
||||
);
|
||||
registrationData.addChild(newChild);
|
||||
@@ -134,23 +149,27 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
try {
|
||||
final XFile? pickedFile = await picker.pickImage(
|
||||
source: ImageSource.gallery, imageQuality: 70, maxWidth: 1024, maxHeight: 1024);
|
||||
source: ImageSource.gallery,
|
||||
imageQuality: 60,
|
||||
maxWidth: 900,
|
||||
maxHeight: 900,
|
||||
);
|
||||
if (pickedFile != null) {
|
||||
if (childIndex < registrationData.children.length) {
|
||||
final oldChild = registrationData.children[childIndex];
|
||||
final updatedChild = ChildData(
|
||||
firstName: oldChild.firstName,
|
||||
lastName: oldChild.lastName,
|
||||
dob: oldChild.dob,
|
||||
photoConsent: oldChild.photoConsent,
|
||||
multipleBirth: oldChild.multipleBirth,
|
||||
isUnbornChild: oldChild.isUnbornChild,
|
||||
imageFile: File(pickedFile.path),
|
||||
cardColor: oldChild.cardColor,
|
||||
);
|
||||
final bytes = await pickedFile.readAsBytes();
|
||||
if (bytes.isEmpty) return;
|
||||
File? file;
|
||||
if (!kIsWeb) {
|
||||
try {
|
||||
final f = File(pickedFile.path);
|
||||
if (await f.exists()) file = f;
|
||||
} catch (_) {}
|
||||
}
|
||||
final updatedChild = oldChild.copyWith(imageBytes: bytes, imageFile: file);
|
||||
registrationData.updateChild(childIndex, updatedChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { print("Erreur image: $e"); }
|
||||
}
|
||||
|
||||
@@ -188,15 +207,8 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
);
|
||||
if (picked != null) {
|
||||
final oldChild = registrationData.children[childIndex];
|
||||
final updatedChild = ChildData(
|
||||
firstName: oldChild.firstName,
|
||||
lastName: oldChild.lastName,
|
||||
dob: "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}",
|
||||
photoConsent: oldChild.photoConsent,
|
||||
multipleBirth: oldChild.multipleBirth,
|
||||
isUnbornChild: oldChild.isUnbornChild,
|
||||
imageFile: oldChild.imageFile,
|
||||
cardColor: oldChild.cardColor,
|
||||
final updatedChild = oldChild.copyWith(
|
||||
dob: "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}",
|
||||
);
|
||||
registrationData.updateChild(childIndex, updatedChild);
|
||||
}
|
||||
@@ -287,40 +299,42 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
// Générer les cartes enfants
|
||||
for (int index = 0; index < registrationData.children.length; index++) ...[
|
||||
ChildCardWidget(
|
||||
key: ValueKey(registrationData.children[index].hashCode),
|
||||
key: ValueKey('parent_register_child_$index'),
|
||||
childData: registrationData.children[index],
|
||||
childIndex: index,
|
||||
onPickImage: () => _pickImage(index, registrationData),
|
||||
onClearImage: () => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(
|
||||
index, c.copyWith(imageFile: null, imageBytes: null));
|
||||
}),
|
||||
onDateSelect: () => _selectDate(context, index, registrationData),
|
||||
onFirstNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: value, lastName: registrationData.children[index].lastName, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onLastNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: registrationData.children[index].firstName, lastName: value, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onFirstNameChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(firstName: value));
|
||||
}),
|
||||
onLastNameChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(lastName: value));
|
||||
}),
|
||||
onGenreChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(genre: value));
|
||||
}),
|
||||
onTogglePhotoConsent: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: newValue,
|
||||
multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
onToggleMultipleBirth: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent,
|
||||
multipleBirth: newValue, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
registrationData.updateChild(index, oldChild.copyWith(photoConsent: newValue));
|
||||
},
|
||||
onToggleIsUnborn: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: DataGenerator.dob(isUnborn: newValue),
|
||||
photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: newValue,
|
||||
imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
var g = oldChild.genre;
|
||||
if (!newValue && g == 'Autre') {
|
||||
g = '';
|
||||
}
|
||||
registrationData.updateChild(
|
||||
index,
|
||||
oldChild.copyWith(isUnbornChild: newValue, genre: g),
|
||||
);
|
||||
},
|
||||
onRemove: () => _removeChild(index, registrationData),
|
||||
canBeRemoved: registrationData.children.length > 1,
|
||||
@@ -343,7 +357,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
|
||||
const SizedBox(height: 30),
|
||||
// Boutons navigation en bas du scroll
|
||||
_buildMobileButtons(context, config, screenSize),
|
||||
_buildMobileButtons(context, config, screenSize, registrationData),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
@@ -393,41 +407,43 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 20.0),
|
||||
child: ChildCardWidget(
|
||||
key: ValueKey(registrationData.children[index].hashCode), // Utiliser une clé basée sur les données
|
||||
key: ValueKey('parent_register_child_$index'),
|
||||
childData: registrationData.children[index],
|
||||
childIndex: index,
|
||||
onPickImage: () => _pickImage(index, registrationData),
|
||||
onClearImage: () => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(
|
||||
index, c.copyWith(imageFile: null, imageBytes: null));
|
||||
}),
|
||||
onDateSelect: () => _selectDate(context, index, registrationData),
|
||||
onFirstNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: value, lastName: registrationData.children[index].lastName, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onLastNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: registrationData.children[index].firstName, lastName: value, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onFirstNameChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(firstName: value));
|
||||
}),
|
||||
onLastNameChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(lastName: value));
|
||||
}),
|
||||
onGenreChanged: (value) => setState(() {
|
||||
final c = registrationData.children[index];
|
||||
registrationData.updateChild(index, c.copyWith(genre: value));
|
||||
}),
|
||||
onTogglePhotoConsent: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: newValue,
|
||||
multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
onToggleMultipleBirth: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent,
|
||||
multipleBirth: newValue, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
registrationData.updateChild(index, oldChild.copyWith(photoConsent: newValue));
|
||||
},
|
||||
onToggleIsUnborn: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: DataGenerator.dob(isUnborn: newValue),
|
||||
photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: newValue,
|
||||
imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
final oldChild = registrationData.children[index];
|
||||
var g = oldChild.genre;
|
||||
if (!newValue && g == 'Autre') {
|
||||
g = '';
|
||||
}
|
||||
registrationData.updateChild(
|
||||
index,
|
||||
oldChild.copyWith(isUnbornChild: newValue, genre: g),
|
||||
);
|
||||
},
|
||||
onRemove: () => _removeChild(index, registrationData),
|
||||
canBeRemoved: registrationData.children.length > 1,
|
||||
),
|
||||
@@ -455,7 +471,12 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
}
|
||||
|
||||
/// Boutons navigation mobile
|
||||
Widget _buildMobileButtons(BuildContext context, DisplayConfig config, Size screenSize) {
|
||||
Widget _buildMobileButtons(
|
||||
BuildContext context,
|
||||
DisplayConfig config,
|
||||
Size screenSize,
|
||||
UserRegistrationData registrationData,
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -483,7 +504,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
text: 'Suivant',
|
||||
style: NavigationButtonStyle.green,
|
||||
onPressed: () {
|
||||
context.go('/parent-register-step4');
|
||||
_normalizeChildrenNamesAndGoToStep4(context, registrationData);
|
||||
},
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
|
||||
Reference in New Issue
Block a user