From 14efccc711a53485ff418f03eb250ae7b0f53633 Mon Sep 17 00:00:00 2001 From: Hanim Date: Tue, 12 Aug 2025 16:16:54 +0200 Subject: [PATCH 1/2] feat: Refactor user AM Part 1 registration data model and update registration screens --- ...art => parent_user_registration_data.dart} | 0 frontend/lib/navigation/app_router.dart | 9 +- .../auth/am/am_register_step1_sceen.dart | 251 +++++++++++++++++- .../parent/parent_register_step1_screen.dart | 2 +- .../parent/parent_register_step2_screen.dart | 2 +- .../parent/parent_register_step3_screen.dart | 2 +- .../parent/parent_register_step4_screen.dart | 2 +- .../parent/parent_register_step5_screen.dart | 2 +- .../screens/auth/register_choice_screen.dart | 2 + frontend/lib/widgets/FormFieldConfig.dart | 114 ++++++++ 10 files changed, 371 insertions(+), 15 deletions(-) rename frontend/lib/models/{user_registration_data.dart => parent_user_registration_data.dart} (100%) create mode 100644 frontend/lib/widgets/FormFieldConfig.dart diff --git a/frontend/lib/models/user_registration_data.dart b/frontend/lib/models/parent_user_registration_data.dart similarity index 100% rename from frontend/lib/models/user_registration_data.dart rename to frontend/lib/models/parent_user_registration_data.dart diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index 397113d..bb431bf 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:p_tits_pas/screens/auth/am/am_register_step1_sceen.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/register_choice_screen.dart'; import '../screens/auth/parent/parent_register_step1_screen.dart'; @@ -8,7 +9,7 @@ import '../screens/auth/parent/parent_register_step3_screen.dart'; import '../screens/auth/parent/parent_register_step4_screen.dart'; import '../screens/auth/parent/parent_register_step5_screen.dart'; import '../screens/home/home_screen.dart'; -import '../models/user_registration_data.dart'; +import '../models/parent_user_registration_data.dart'; class AppRouter { static const String login = '/login'; @@ -18,6 +19,8 @@ class AppRouter { static const String parentRegisterStep3 = '/parent-register/step3'; static const String parentRegisterStep4 = '/parent-register/step4'; static const String parentRegisterStep5 = '/parent-register/step5'; + + static const String amRegisterStep1 = '/am-register/step1'; static const String home = '/home'; static Route generateRoute(RouteSettings settings) { @@ -74,6 +77,10 @@ class AppRouter { } slideTransition = true; break; + case amRegisterStep1: + screen = const AmRegisterStep1Screen(); + slideTransition = true; + break; case home: screen = const HomeScreen(); break; diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 9fb1909..0664857 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -1,17 +1,250 @@ - import 'package:flutter/material.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; +import 'package:p_tits_pas/models/parent_user_registration_data.dart'; +import 'package:p_tits_pas/utils/data_generator.dart'; +import 'package:p_tits_pas/widgets/FormFieldConfig.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:math' as math; + + +class AmRegisterStep1Screen extends StatefulWidget { + const AmRegisterStep1Screen({super.key}); + @override + State createState() => _AmRegisterStep1ScreenState(); +} + +class _AmRegisterStep1ScreenState extends State { + final _formKey = GlobalKey(); + late UserRegistrationData _registrationData; + + final _lastNameController = TextEditingController(); + final _firstNameController = TextEditingController(); + final _phoneController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + final _addressController = TextEditingController(); + final _postalCodeController = TextEditingController(); + final _cityController = TextEditingController(); -class AmRegisterStep1Screen extends StatelessWidget { - const AmRegisterStep1Screen({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Étape 1 - Inscription AM'), + void initState() { + super.initState(); + _registrationData = UserRegistrationData(); + _generateAndFillData(); + } + + void _generateAndFillData() { + final String genFirstName = DataGenerator.firstName(); + final String genLastName = DataGenerator.lastName(); + + _addressController.text = DataGenerator.address(); + _postalCodeController.text = DataGenerator.postalCode(); + _cityController.text = DataGenerator.city(); + _firstNameController.text = genFirstName; + _lastNameController.text = genLastName; + _phoneController.text = DataGenerator.phone(); + _emailController.text = DataGenerator.email(genFirstName, genLastName); + _passwordController.text = DataGenerator.password(); + _confirmPasswordController.text = _passwordController.text; + } + + @override + void dispose() { + _lastNameController.dispose(); + _firstNameController.dispose(); + _phoneController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + _addressController.dispose(); + _postalCodeController.dispose(); + _cityController.dispose(); + super.dispose(); + } + + List> get formFields => [ + [ + ModularFormField( + label: 'Nom', + hint: 'Votre nom de famille', + controller: _lastNameController, + isRequired: true, + flex: 12, ), - body: const Center( - child: Text('Contenu de l\'étape 1'), + ModularFormField( + label: 'Prénom', + hint: 'Votre prénom', + controller: _firstNameController, + isRequired: true, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Téléphone', + hint: 'Votre numéro de téléphone', + controller: _phoneController, + keyboardType: TextInputType.phone, + flex: 12, + ), + ModularFormField( + label: 'Email', + hint: 'Votre adresse email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Mot de passe', + hint: 'Votre mot de passe', + controller: _passwordController, + isPassword: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Mot de passe requis'; + if (value.length < 6) return '6 caractères minimum'; + return null; + }, + isRequired: true, + flex: 12, + ), + ModularFormField( + label: 'Confirmer le mot de passe', + hint: 'Confirmez votre mot de passe', + controller: _confirmPasswordController, + isPassword: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Mot de passe requis'; + if (value != _passwordController.text) return 'Les mots de passe ne correspondent pas'; + return null; + }, + isRequired: true, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Adresse (N° et Rue)', + hint: 'Numéro et nom de votre rue', + controller: _addressController, + isRequired: true, + ), + ], + [ + ModularFormField( + label: 'Code postal', + hint: 'Votre code postal', + controller: _postalCodeController, + keyboardType: TextInputType.number, + isRequired: true, + flex: 1, + ), + ModularFormField( + label: 'Ville', + hint: 'Votre ville', + controller: _cityController, + flex: 4, + isRequired: true, + ), + ], + ]; + + void _handleSubmit() { + if (_formKey.currentState?.validate() ?? false) { + _registrationData.updateParent1( + ParentData( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + address: _addressController.text, + postalCode: _postalCodeController.text, + city: _cityController.text, + phone: _phoneController.text, + email: _emailController.text, + password: _passwordController.text, + ), + ); + Navigator.pushNamed(context, '/am-register/step2', arguments: _registrationData); + } + } + + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + + return Scaffold( + body: Stack( + children: [ + Positioned.fill( + child: Image.asset( + 'assets/images/paper2.png', + fit: BoxFit.cover, + repeat: ImageRepeat.repeat, + ), + ), + Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Étape 1/5', + style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 10), + Text( + 'Informations de l\'assistante maternelle', + style: GoogleFonts.merienda( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + Container( + width: screenSize.width * 0.6, + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50), + constraints: const BoxConstraints(minHeight: 570), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(CardColorHorizontal.lavender.path), + fit: BoxFit.fill, + ), + ), + child: ModularForm( + formKey: _formKey, + fieldGroups: formFields, + ), + ), + ], + ), + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + left: 40, + child: IconButton( + icon: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(math.pi), + child: Image.asset('assets/images/chevron_right.png', height: 40), + ), + onPressed: () => Navigator.pop(context), + tooltip: 'Retour', + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + right: 40, + child: IconButton( + icon: Image.asset('assets/images/chevron_right.png', height: 40), + onPressed: _handleSubmit, + tooltip: 'Suivant', + ), + ), + ], ), ); } diff --git a/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart index 6f04062..9058360 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../models/parent_user_registration_data.dart'; // Import du modèle de données import '../../../utils/data_generator.dart'; // Import du générateur de données import '../../../widgets/custom_app_text_field.dart'; // Import du widget CustomAppTextField import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart index f1008c0..38879fc 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../../models/user_registration_data.dart'; // Import du modèle +import '../../../models/parent_user_registration_data.dart'; // Import du modèle import '../../../utils/data_generator.dart'; // Import du générateur import '../../../widgets/custom_app_text_field.dart'; // Import du widget import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart index 4797050..0dbfa8d 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart @@ -9,7 +9,7 @@ import 'dart:io' show File, Platform; // Ajout de Platform import 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb import '../../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField import '../../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox -import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../models/parent_user_registration_data.dart'; // Import du modèle de données import '../../../utils/data_generator.dart'; // Import du générateur import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart index c4a009e..66d505c 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart @@ -4,7 +4,7 @@ import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; // Import import 'dart:math' as math; // Pour la rotation du chevron import 'package:p_tits_pas/widgets/app_custom_checkbox.dart'; // Import de la checkbox personnalisée // import 'package:p_tits_pas/models/placeholder_registration_data.dart'; // Remplacé -import '../../../models/user_registration_data.dart'; // Import du vrai modèle +import '../../../models/parent_user_registration_data.dart'; // Import du vrai modèle import '../../../utils/data_generator.dart'; // Import du générateur import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart index adedd3c..7f85fec 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../../../models/user_registration_data.dart'; // Utilisation du vrai modèle +import '../../../models/parent_user_registration_data.dart'; // Utilisation du vrai modèle import '../../../widgets/image_button.dart'; // Import du ImageButton import '../../../models/card_assets.dart'; // Import des enums de cartes import 'package:flutter/foundation.dart' show kIsWeb; diff --git a/frontend/lib/screens/auth/register_choice_screen.dart b/frontend/lib/screens/auth/register_choice_screen.dart index 0b6bd8a..457ef1f 100644 --- a/frontend/lib/screens/auth/register_choice_screen.dart +++ b/frontend/lib/screens/auth/register_choice_screen.dart @@ -100,6 +100,8 @@ class RegisterChoiceScreen extends StatelessWidget { onPressed: () { // TODO: Naviguer vers l'écran d'inscription assmat print('Choix: Assistante Maternelle'); + Navigator.pushNamed(context, '/am-register/step1'); + }, ), ], diff --git a/frontend/lib/widgets/FormFieldConfig.dart b/frontend/lib/widgets/FormFieldConfig.dart new file mode 100644 index 0000000..6e14cc7 --- /dev/null +++ b/frontend/lib/widgets/FormFieldConfig.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/widgets/custom_app_text_field.dart'; + +class ModularFormField { + final String label; + final String hint; + final TextEditingController controller; + final TextInputType? keyboardType; + final bool isPassword; + final String? Function(String?)? validator; + final bool isRequired; + final int flex; + + ModularFormField({ + required this.label, + required this.hint, + required this.controller, + this.keyboardType, + this.isPassword = false, + this.validator, + this.isRequired = false, + this.flex = 1, + }); +} + +class ModularForm extends StatelessWidget { + final List> fieldGroups; + final GlobalKey formKey; + final double? width; + final EdgeInsets padding; + final String? title; + final VoidCallback? onSubmit; + final String submitLabel; + + const ModularForm({ + super.key, + required this.fieldGroups, + required this.formKey, + this.width, + this.padding = const EdgeInsets.all(20), + this.title, + this.onSubmit, + this.submitLabel = "Suivant", + }); + + @override + Widget build(BuildContext context) { + return Container( + width: width ?? MediaQuery.of(context).size.width * 0.6, + padding: padding, + child: Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) ...[ + Text( + title!, + style: GoogleFonts.merienda( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), + ], + ...fieldGroups.map((group) { + return Column( + children: [ + Row( + children: group.asMap().entries.map((entry) { + final index = entry.key; + final field = entry.value; + + return [ + Expanded( + flex: field.flex, + child: CustomAppTextField( + controller: field.controller, + labelText: field.label, + hintText: field.hint, + obscureText: field.isPassword, + keyboardType: field.keyboardType ?? TextInputType.text, + validator: field.validator, + style: CustomAppTextFieldStyle.beige, + fieldWidth: double.infinity, // CORRECTION PRINCIPALE + ), + ), + // Ajouter un espaceur entre les champs (sauf pour le dernier) + if (index < group.length - 1) + const Expanded( + flex: 1, + child: SizedBox(), // Espacement de 4% comme dans l'original + ), + ]; + }).expand((element) => element).toList(), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + if (onSubmit != null) + Center( + child: ElevatedButton( + onPressed: onSubmit, + child: Text(submitLabel), + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file From acda4244a9c38ec089b72628e351f4abede59bce Mon Sep 17 00:00:00 2001 From: Hanim Date: Thu, 14 Aug 2025 11:33:40 +0200 Subject: [PATCH 2/2] feat: Update registration data model for Childminder and adjust related screen AM logic --- .../lib/models/am_user_registration_data.dart | 96 +++++++++++++++++++ .../auth/am/am_register_step1_sceen.dart | 22 +++-- 2 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 frontend/lib/models/am_user_registration_data.dart diff --git a/frontend/lib/models/am_user_registration_data.dart b/frontend/lib/models/am_user_registration_data.dart new file mode 100644 index 0000000..7a04ff2 --- /dev/null +++ b/frontend/lib/models/am_user_registration_data.dart @@ -0,0 +1,96 @@ +import 'dart:io'; + +class ChildminderId { + String firstName; + String lastName; + String address; + String postalCode; + String city; + String phone; + String email; + String password; + File? profilePicture; + bool photoConsent; + + ChildminderId({ + this.firstName = '', + this.lastName = '', + this.address = '', + this.postalCode = '', + this.city = '', + this.phone = '', + this.email = '', + this.password = '', + this.profilePicture, + this.photoConsent = false, + }); +} + +class ChildminderProfessional { + String dateOfBirth; + String birthCity; + String birthCountry; + String socialSecurityNumber; // NIR + String agreementNumber; + int maxChildren; + + ChildminderProfessional({ + this.dateOfBirth = '', + this.birthCity = '', + this.birthCountry = '', + this.socialSecurityNumber = '', + this.agreementNumber = '', + this.maxChildren = 1, + }); +} + +class ChildminderRegistrationData { + ChildminderId identity; + ChildminderProfessional professional; + String presentationMessage; + bool cguAccepted; + bool isPhotoRequired; + + ChildminderRegistrationData({ + ChildminderId? identityData, + ChildminderProfessional? professionalData, + this.presentationMessage = '', + this.cguAccepted = false, + this.isPhotoRequired = false, + }) : identity = identityData ?? ChildminderId(), + professional = professionalData ?? ChildminderProfessional(); + + void updateIdentity(ChildminderId data) { + identity = data; + } + + void updateProfessional(ChildminderProfessional data) { + professional = data; + } + + void updatePresentation(String message) { + presentationMessage = message; + } + + void acceptCGU() { + cguAccepted = true; + } + + bool get isComplete { + return identity.firstName.isNotEmpty && + identity.lastName.isNotEmpty && + identity.address.isNotEmpty && + identity.postalCode.isNotEmpty && + identity.city.isNotEmpty && + identity.phone.isNotEmpty && + identity.email.isNotEmpty && + identity.password.isNotEmpty && + professional.dateOfBirth.isNotEmpty && + professional.birthCity.isNotEmpty && + professional.birthCountry.isNotEmpty && + professional.socialSecurityNumber.isNotEmpty && + professional.agreementNumber.isNotEmpty && + cguAccepted && + (!isPhotoRequired || (identity.profilePicture != null && identity.photoConsent)); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 0664857..85490b1 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; import 'package:p_tits_pas/models/card_assets.dart'; -import 'package:p_tits_pas/models/parent_user_registration_data.dart'; import 'package:p_tits_pas/utils/data_generator.dart'; import 'package:p_tits_pas/widgets/FormFieldConfig.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -15,7 +15,7 @@ class AmRegisterStep1Screen extends StatefulWidget { class _AmRegisterStep1ScreenState extends State { final _formKey = GlobalKey(); - late UserRegistrationData _registrationData; + late ChildminderRegistrationData _registrationData; final _lastNameController = TextEditingController(); final _firstNameController = TextEditingController(); @@ -27,11 +27,15 @@ class _AmRegisterStep1ScreenState extends State { final _postalCodeController = TextEditingController(); final _cityController = TextEditingController(); + // File? _selectedImage; + // bool _photoConsent = false; + // final ImagePicker _picker = ImagePicker(); + @override void initState() { super.initState(); - _registrationData = UserRegistrationData(); + _registrationData = ChildminderRegistrationData(); _generateAndFillData(); } @@ -152,10 +156,10 @@ class _AmRegisterStep1ScreenState extends State { ], ]; - void _handleSubmit() { + void _handleSubmit() { if (_formKey.currentState?.validate() ?? false) { - _registrationData.updateParent1( - ParentData( + _registrationData.updateIdentity( + ChildminderId( firstName: _firstNameController.text, lastName: _lastNameController.text, address: _addressController.text, @@ -166,10 +170,12 @@ class _AmRegisterStep1ScreenState extends State { password: _passwordController.text, ), ); - Navigator.pushNamed(context, '/am-register/step2', arguments: _registrationData); + Navigator.pushNamed(context, '/am-register/step2', + arguments: _registrationData); } } + @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; @@ -189,7 +195,7 @@ class _AmRegisterStep1ScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Étape 1/5', + 'Étape 1/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), ), const SizedBox(height: 10),