feat: Intégration du frontend Flutter depuis YNOV
- Framework: Flutter web - Pages: Login, inscription, dashboards - Services: API client, authentification, gestion d'état - Intégration avec backend NestJS - Dockerfile pour déploiement web
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
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/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 <AmRegisterStep1Screen> createState() => _AmRegisterStep1ScreenState();
|
||||
}
|
||||
|
||||
class _AmRegisterStep1ScreenState extends State<AmRegisterStep1Screen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late ChildminderRegistrationData _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();
|
||||
|
||||
// File? _selectedImage;
|
||||
// bool _photoConsent = false;
|
||||
// final ImagePicker _picker = ImagePicker();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_registrationData = ChildminderRegistrationData();
|
||||
_generateAndFillData();
|
||||
}
|
||||
|
||||
void _generateAndFillData() {
|
||||
final String genFirstName = DataGenerator.firstName();
|
||||
final String genLastName = DataGenerator.lastName();
|
||||
final String genAddress = DataGenerator.address();
|
||||
final String genPostalCode = DataGenerator.postalCode();
|
||||
final String genCity = DataGenerator.city();
|
||||
final String genPhone = DataGenerator.phone();
|
||||
final String genEmail = DataGenerator.email(genFirstName, genLastName);
|
||||
final String genPassword = DataGenerator.password();
|
||||
|
||||
_addressController.text = genAddress;
|
||||
_postalCodeController.text = genPostalCode;
|
||||
_cityController.text = genCity;
|
||||
_firstNameController.text = genFirstName;
|
||||
_lastNameController.text = genLastName;
|
||||
_phoneController.text = genPhone;
|
||||
_emailController.text = genEmail;
|
||||
_passwordController.text = genPassword;
|
||||
_confirmPasswordController.text = genPassword;
|
||||
|
||||
setState(() {
|
||||
_registrationData.updateIdentity(
|
||||
ChildminderId(
|
||||
firstName: genFirstName,
|
||||
lastName: genLastName,
|
||||
address: genAddress,
|
||||
postalCode: genPostalCode,
|
||||
city: genCity,
|
||||
phone: genPhone,
|
||||
email: genEmail,
|
||||
password: genPassword.trim(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_lastNameController.dispose();
|
||||
_firstNameController.dispose();
|
||||
_phoneController.dispose();
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
_confirmPasswordController.dispose();
|
||||
_addressController.dispose();
|
||||
_postalCodeController.dispose();
|
||||
_cityController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<List<ModularFormField>> get formFields => [
|
||||
[
|
||||
ModularFormField(
|
||||
label: 'Nom',
|
||||
hint: 'Votre nom de famille',
|
||||
controller: _lastNameController,
|
||||
isRequired: true,
|
||||
flex: 12,
|
||||
),
|
||||
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.updateIdentity(
|
||||
ChildminderId(
|
||||
firstName: _firstNameController.text,
|
||||
lastName: _lastNameController.text,
|
||||
address: _addressController.text,
|
||||
postalCode: _postalCodeController.text,
|
||||
city: _cityController.text,
|
||||
phone: _phoneController.text,
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
),
|
||||
);
|
||||
print('Vérification des données:');
|
||||
print('Adresse: ${_registrationData.identity.address}');
|
||||
print('Nom: ${_registrationData.identity.lastName}');
|
||||
print('Prénom: ${_registrationData.identity.firstName}');
|
||||
Navigator.pushNamed(context, '/am-register/step2',
|
||||
arguments: _registrationData);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
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/4',
|
||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Informations d\'identité 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',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.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/utils/data_generator.dart';
|
||||
import 'package:p_tits_pas/widgets/FormFieldConfig.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class AmRegisterStep2Screen extends StatefulWidget {
|
||||
final ChildminderRegistrationData registrationData;
|
||||
const AmRegisterStep2Screen({super.key, required this.registrationData});
|
||||
|
||||
@override
|
||||
State<AmRegisterStep2Screen> createState() => _AmRegisterStep2ScreenState();
|
||||
}
|
||||
|
||||
class _AmRegisterStep2ScreenState extends State<AmRegisterStep2Screen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late ChildminderRegistrationData _registrationData;
|
||||
|
||||
final _dateOfBirthController = TextEditingController();
|
||||
final _birthCityController = TextEditingController();
|
||||
final _birthCountryController = TextEditingController();
|
||||
final _socialSecurityController = TextEditingController();
|
||||
final _agreementNumberController = TextEditingController();
|
||||
final _maxChildrenController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_registrationData = widget.registrationData;
|
||||
_generateAndFillData();
|
||||
}
|
||||
|
||||
void _generateAndFillData() {
|
||||
_dateOfBirthController.text = DataGenerator.birthDate();
|
||||
_birthCityController.text = DataGenerator.city();
|
||||
_birthCountryController.text = "France";
|
||||
_socialSecurityController.text = DataGenerator.socialSecurityNumber();
|
||||
_agreementNumberController.text = DataGenerator.agreementNumber();
|
||||
_maxChildrenController.text = "3";
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dateOfBirthController.dispose();
|
||||
_birthCityController.dispose();
|
||||
_birthCountryController.dispose();
|
||||
_socialSecurityController.dispose();
|
||||
_agreementNumberController.dispose();
|
||||
_maxChildrenController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String? _validateSocialSecurity(String? value) {
|
||||
if (value == null || value.isEmpty)
|
||||
return 'Numéro de sécurité sociale requis';
|
||||
|
||||
// Supprime les espaces pour la validation
|
||||
String cleanValue = value.replaceAll(' ', '');
|
||||
|
||||
// Vérifie que c'est bien 13 ou 15 chiffres
|
||||
if (cleanValue.length != 13 && cleanValue.length != 15) {
|
||||
return 'Format invalide (13 ou 15 chiffres)';
|
||||
}
|
||||
|
||||
if (!RegExp(r'^[0-9]+$').hasMatch(cleanValue)) {
|
||||
return 'Seuls les chiffres sont autorisés';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
List<List<ModularFormField>> get formFields => [
|
||||
[
|
||||
ModularFormField(
|
||||
label: 'Date de naissance',
|
||||
hint: 'JJ/MM/AAAA',
|
||||
controller: _dateOfBirthController,
|
||||
keyboardType: TextInputType.datetime,
|
||||
isRequired: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty)
|
||||
return 'Date de naissance requise';
|
||||
// Validation basique du format de date
|
||||
if (!RegExp(r'^[0-3][0-9]/[0-1][0-9]/[1-2][0-9]{3}$')
|
||||
.hasMatch(value)) {
|
||||
return 'Format invalide (JJ/MM/AAAA)';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
flex: 12,
|
||||
),
|
||||
],
|
||||
[
|
||||
ModularFormField(
|
||||
label: 'Ville de naissance',
|
||||
hint: 'Votre ville de naissance',
|
||||
controller: _birthCityController,
|
||||
isRequired: true,
|
||||
flex: 12,
|
||||
),
|
||||
ModularFormField(
|
||||
label: 'Pays de naissance',
|
||||
hint: 'Votre pays de naissance',
|
||||
controller: _birthCountryController,
|
||||
isRequired: true,
|
||||
flex: 12,
|
||||
),
|
||||
],
|
||||
[
|
||||
ModularFormField(
|
||||
label: 'Numéro de Sécurité Sociale (NIR)',
|
||||
hint: '1234567890123',
|
||||
controller: _socialSecurityController,
|
||||
keyboardType: TextInputType.number,
|
||||
isRequired: true,
|
||||
validator: _validateSocialSecurity,
|
||||
),
|
||||
],
|
||||
[
|
||||
ModularFormField(
|
||||
label: 'Numéro d\'agrément',
|
||||
hint: 'Votre numéro d\'agrément',
|
||||
controller: _agreementNumberController,
|
||||
isRequired: true,
|
||||
flex: 12,
|
||||
),
|
||||
ModularFormField(
|
||||
label: 'Nombre d\'enfants max',
|
||||
hint: 'Ex: 3',
|
||||
controller: _maxChildrenController,
|
||||
keyboardType: TextInputType.number,
|
||||
isRequired: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return 'Nombre requis';
|
||||
int? number = int.tryParse(value);
|
||||
if (number == null || number < 1 || number > 6) {
|
||||
return 'Entre 1 et 6 enfants';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
flex: 6,
|
||||
),
|
||||
],
|
||||
];
|
||||
void _handleSubmit() {
|
||||
print('Vérification des données2:');
|
||||
print('Adresse: ${_registrationData.identity.address}');
|
||||
print('Nom: ${_registrationData.identity.lastName}');
|
||||
print('Prénom: ${_registrationData.identity.firstName}');
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
_registrationData.updateProfessional(
|
||||
ChildminderProfessional(
|
||||
dateOfBirth: _dateOfBirthController.text,
|
||||
birthCity: _birthCityController.text,
|
||||
birthCountry: _birthCountryController.text,
|
||||
socialSecurityNumber: _socialSecurityController.text,
|
||||
agreementNumber: _agreementNumberController.text,
|
||||
maxChildren: int.tryParse(_maxChildrenController.text) ?? 1,
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.pushNamed(context, '/am-register/step3',
|
||||
arguments: _registrationData);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
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 2/4',
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Informations professionnelles 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.peach.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',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.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/widgets/app_custom_checkbox.dart';
|
||||
import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
|
||||
class AmRegisterStep3Screen extends StatefulWidget {
|
||||
final ChildminderRegistrationData registrationData;
|
||||
const AmRegisterStep3Screen({super.key, required this.registrationData});
|
||||
|
||||
@override
|
||||
State<AmRegisterStep3Screen> createState() => _AmRegisterStep3ScreenState();
|
||||
}
|
||||
|
||||
class _AmRegisterStep3ScreenState extends State<AmRegisterStep3Screen> {
|
||||
|
||||
late ChildminderRegistrationData _registrationData;
|
||||
final _presentationMessageController = TextEditingController();
|
||||
bool _cguAccepted = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_registrationData = widget.registrationData;
|
||||
_presentationMessageController.text = _registrationData.presentationMessage;
|
||||
// _cguAccepted = _registrationData.cguAccepted;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_presentationMessageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showCGUModal() {
|
||||
const String loremIpsumText = '''
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.
|
||||
|
||||
Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.
|
||||
|
||||
Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.
|
||||
|
||||
Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.
|
||||
|
||||
Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna. Etiam et felis dolor.
|
||||
|
||||
Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
|
||||
|
||||
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||
''';
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // L'utilisateur doit utiliser le bouton
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Conditions Générales d\'Utilisation',
|
||||
style: GoogleFonts.merienda(fontWeight: FontWeight.bold),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(dialogContext).size.width * 0.7, // 70% de la largeur de l'écran
|
||||
height: MediaQuery.of(dialogContext).size.height * 0.6, // 60% de la hauteur de l'écran
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
loremIpsumText,
|
||||
style: GoogleFonts.merienda(fontSize: 13),
|
||||
textAlign: TextAlign.justify,
|
||||
),
|
||||
),
|
||||
),
|
||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(dialogContext).primaryColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
|
||||
),
|
||||
child: Text(
|
||||
'Valider et Accepter',
|
||||
style: GoogleFonts.merienda(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop(); // Ferme la modale
|
||||
setState(() {
|
||||
_cguAccepted = true; // Met à jour l'état
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final cardWidth = screenSize.width * 0.6;
|
||||
final double imageAspectRatio = 2.0;
|
||||
final cardHeight = cardWidth / imageAspectRatio;
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 50.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Étape 3/4',
|
||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Message à destination du gestionnaire pour justifier votre demande ou ajouter des précisions',
|
||||
style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(CardColorHorizontal.green.path),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomDecoratedTextField(
|
||||
controller: _presentationMessageController,
|
||||
hintText: 'Écrivez ici pour motiver votre demande...',
|
||||
fieldHeight: cardHeight * 0.6,
|
||||
maxLines: 10,
|
||||
expandDynamically: true,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (!_cguAccepted) {
|
||||
_showCGUModal();
|
||||
}
|
||||
},
|
||||
child: AppCustomCheckbox(
|
||||
label: 'J\'accepte les conditions générales d\'utilisation',
|
||||
value: _cguAccepted,
|
||||
onChanged: (newValue) {
|
||||
if (!_cguAccepted) {
|
||||
_showCGUModal();
|
||||
} else {
|
||||
setState(() => _cguAccepted = false);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Chevrons de navigation
|
||||
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: _cguAccepted
|
||||
? () {
|
||||
_registrationData.updatePresentation(_presentationMessageController.text);
|
||||
_registrationData.acceptCGU();
|
||||
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
'/am-register/step4',
|
||||
arguments: _registrationData
|
||||
);
|
||||
}
|
||||
: null,
|
||||
tooltip: 'Suivant',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.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/widgets/Summary.dart';
|
||||
import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart';
|
||||
import 'package:p_tits_pas/widgets/image_button.dart';
|
||||
|
||||
Widget _buildDisplayFieldValue(BuildContext context, String label, String value, {bool multiLine = false, double fieldHeight = 50.0, double labelFontSize = 18.0}) {
|
||||
const FontWeight labelFontWeight = FontWeight.w600;
|
||||
|
||||
// Ne pas afficher le label si labelFontSize est 0 ou si label est vide
|
||||
bool showLabel = label.isNotEmpty && labelFontSize > 0;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showLabel)
|
||||
Text(label, style: GoogleFonts.merienda(fontSize: labelFontSize, fontWeight: labelFontWeight)),
|
||||
if (showLabel)
|
||||
const SizedBox(height: 4),
|
||||
// Utiliser Expanded si multiLine et pas de hauteur fixe, sinon Container
|
||||
multiLine && fieldHeight == null
|
||||
? Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/input_field_bg.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView( // Pour le défilement si le texte dépasse
|
||||
child: Text(
|
||||
value.isNotEmpty ? value : '-',
|
||||
style: GoogleFonts.merienda(fontSize: labelFontSize > 0 ? labelFontSize : 18.0), // Garder une taille de texte par défaut si label caché
|
||||
maxLines: null, // Permettre un nombre illimité de lignes
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: double.infinity,
|
||||
height: multiLine ? null : fieldHeight,
|
||||
constraints: multiLine ? BoxConstraints(minHeight: fieldHeight) : null,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/input_field_bg.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
value.isNotEmpty ? value : '-',
|
||||
style: GoogleFonts.merienda(fontSize: labelFontSize > 0 ? labelFontSize : 18.0),
|
||||
maxLines: multiLine ? null : 1,
|
||||
overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class AmRegisterStep4Screen extends StatelessWidget {
|
||||
final ChildminderRegistrationData registrationData;
|
||||
|
||||
const AmRegisterStep4Screen({super.key, required this.registrationData});
|
||||
|
||||
Widget _buildAm1Card(BuildContext context, ChildminderRegistrationData data) {
|
||||
const double verticalSpacing = 28.0; // Espacement vertical augmenté
|
||||
const double labelFontSize = 22.0; // Taille de label augmentée
|
||||
|
||||
List<Widget> details = [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Nom:", data.identity.lastName, labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Prénom:", data.identity.firstName, labelFontSize: labelFontSize)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Téléphone:", data.identity.phone, labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Email:", data.identity.email, multiLine: true, labelFontSize: labelFontSize)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Adresse:", "${data.identity.address}\n${data.identity.postalCode} ${data.identity.city}".trim(), labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
];
|
||||
return SummaryCard(
|
||||
backgroundImagePath: CardColorHorizontal.peach.path,
|
||||
title: 'Informations d’identité',
|
||||
content: details,
|
||||
onEdit: () => Navigator.of(context).pushNamed('/am-register/step1', arguments: registrationData),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAm2Card(BuildContext context, ChildminderRegistrationData data) {
|
||||
const double verticalSpacing = 28.0;
|
||||
const double labelFontSize = 22.0;
|
||||
|
||||
List<Widget> myDetails = [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Date de naissance:", data.professional.dateOfBirth, labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Ville de naissance:", data.professional.birthCity, labelFontSize: labelFontSize)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Pays de naissance:", data.professional.birthCountry, labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Numéro de sécurité sociale:", data.professional.socialSecurityNumber, labelFontSize: labelFontSize)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Numéro d'agrément:", data.professional.agreementNumber, labelFontSize: labelFontSize)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: _buildDisplayFieldValue(context, "Nombre d'enfants maximum:", data.professional.maxChildren.toString(), labelFontSize: labelFontSize)),
|
||||
],
|
||||
),
|
||||
];
|
||||
return SummaryCard(
|
||||
backgroundImagePath: CardColorHorizontal.lavender.path,
|
||||
title: 'Informations professionnelles',
|
||||
content: myDetails,
|
||||
onEdit: () => Navigator.of(context)
|
||||
.pushNamed('/am-register/step2', arguments: registrationData),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMotivationCard(BuildContext context, ChildminderRegistrationData data) {
|
||||
return SummaryCard(
|
||||
backgroundImagePath: CardColorHorizontal.green.path,
|
||||
title: 'Motivation',
|
||||
content: [
|
||||
Expanded(child: CustomDecoratedTextField(
|
||||
controller: TextEditingController(text: data.presentationMessage),
|
||||
hintText: 'Parlez-nous de votre motivation',
|
||||
fieldHeight: 200,
|
||||
maxLines: 10,
|
||||
expandDynamically: true,
|
||||
readOnly: true,
|
||||
fontSize: 18.0,)),
|
||||
],
|
||||
onEdit: () => Navigator.of(context)
|
||||
.pushNamed('/am-register/step3', arguments: registrationData),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
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.repeatY),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: screenSize.width / 4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('Etape 4/4',
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 16, color: Colors.black54)),
|
||||
const SizedBox(height: 20),
|
||||
Text('Récapitulatif de votre demande',
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87),
|
||||
textAlign: TextAlign.center),
|
||||
const SizedBox(height: 30),
|
||||
_buildAm1Card(context, registrationData),
|
||||
const SizedBox(height: 20),
|
||||
if (registrationData.professional != null) ...[
|
||||
_buildAm2Card(context, registrationData),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
_buildMotivationCard(context, registrationData),
|
||||
const SizedBox(height: 40),
|
||||
ImageButton(
|
||||
bg: 'assets/images/btn_green.png',
|
||||
text: 'Soumettre ma demande',
|
||||
textColor: const Color(0xFF2D6A4F),
|
||||
width: 350,
|
||||
height: 50,
|
||||
fontSize: 18,
|
||||
onPressed: () {
|
||||
// Vérification des données requises
|
||||
_showConfirmationModal(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
left: 40,
|
||||
child: IconButton(
|
||||
icon: Transform.flip(
|
||||
flipX: true,
|
||||
child: Image.asset('assets/images/chevron_right.png',
|
||||
height: 40)),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
tooltip: 'Retour',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showConfirmationModal(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Demande enregistrée',
|
||||
style: GoogleFonts.merienda(fontWeight: FontWeight.bold),
|
||||
),
|
||||
content: Text(
|
||||
'Votre dossier a bien été pris en compte. Un gestionnaire le validera bientôt.',
|
||||
style: GoogleFonts.merienda(fontSize: 14),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('OK',
|
||||
style: GoogleFonts.merienda(fontWeight: FontWeight.bold)),
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop();
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil('/login', (route) => false);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:p_tits_pas/services/api/tokenService.dart';
|
||||
import 'package:p_tits_pas/services/auth_service.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:p_tits_pas/services/bug_report_service.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../widgets/image_button.dart';
|
||||
import '../../widgets/custom_app_text_field.dart';
|
||||
|
||||
@@ -19,6 +20,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final AuthService _authService = AuthService();
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -47,6 +50,89 @@ class _LoginPageState extends State<LoginPage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _handleLogin() async {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await _authService.login(
|
||||
_emailController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
print('Login response: ${response}');
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Navigation selon le rôle
|
||||
final role = await TokenService.getRole();
|
||||
print('User role: $role');
|
||||
if (role != null) {
|
||||
switch (role.toLowerCase()) {
|
||||
case 'parent':
|
||||
Navigator.pushReplacementNamed(context, '/parent-dashboard');
|
||||
break;
|
||||
case 'assistante_maternelle':
|
||||
Navigator.pushReplacementNamed(
|
||||
context, '/assistante_maternelle_dashboard');
|
||||
break;
|
||||
case 'super_admin' || 'administrateur':
|
||||
Navigator.pushReplacementNamed(context, '/admin_dashboard');
|
||||
break;
|
||||
case 'gestionnaire':
|
||||
Navigator.pushReplacementNamed(
|
||||
context, '/gestionnaire_dashboard');
|
||||
break;
|
||||
default:
|
||||
_showErrorSnackBar('Rôle utilisateur non reconnu: $role');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_showErrorSnackBar('Rôle utilisateur non trouvé');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Login error: $e');
|
||||
if (!mounted) return;
|
||||
String errorMessage = e.toString();
|
||||
String errorString = e.toString();
|
||||
if (errorString.contains('Failed to login:')) {
|
||||
// Extraire le message d'erreur réel
|
||||
errorMessage =
|
||||
errorString.replaceFirst('Exception: Failed to login: ', '');
|
||||
}
|
||||
|
||||
_showErrorSnackBar(errorMessage);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false; // AJOUT : Fin du chargement
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showErrorSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 4), // Plus long pour lire l'erreur
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSuccessSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -54,7 +140,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// Version desktop (web)
|
||||
if (kIsWeb) {
|
||||
|
||||
// if (kIsWeb) {
|
||||
final w = constraints.maxWidth;
|
||||
final h = constraints.maxHeight;
|
||||
|
||||
@@ -67,7 +154,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
final imageDimensions = snapshot.data!;
|
||||
final imageHeight = h;
|
||||
final imageWidth = imageHeight * (imageDimensions.width / imageDimensions.height);
|
||||
final imageWidth = imageHeight *
|
||||
(imageDimensions.width / imageDimensions.height);
|
||||
final remainingWidth = w - imageWidth;
|
||||
final leftMargin = remainingWidth / 4;
|
||||
|
||||
@@ -96,10 +184,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: w * 0.6, // 60% de la largeur de l'écran
|
||||
height: h * 0.5, // 50% de la hauteur de l'écran
|
||||
width: w * 0.6, // 60% de la largeur de l'écran
|
||||
height: h * 0.5, // 50% de la hauteur de l'écran
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(w * 0.02), // 2% de padding
|
||||
padding: EdgeInsets.all(w * 0.02), // 2% de padding
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
@@ -118,6 +206,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
style: CustomAppTextFieldStyle.lavande,
|
||||
fieldHeight: 53,
|
||||
fieldWidth: double.infinity,
|
||||
enabled: !_isLoading,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
@@ -131,6 +220,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
style: CustomAppTextFieldStyle.jaune,
|
||||
fieldHeight: 53,
|
||||
fieldWidth: double.infinity,
|
||||
enabled: !_isLoading,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -138,17 +228,21 @@ class _LoginPageState extends State<LoginPage> {
|
||||
const SizedBox(height: 20),
|
||||
// Bouton centré
|
||||
Center(
|
||||
child: ImageButton(
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 300,
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: ImageButton(
|
||||
bg: 'assets/images/btn_green.png',
|
||||
width: 300,
|
||||
height: 40,
|
||||
text: 'Se connecter',
|
||||
textColor: const Color(0xFF2D6A4F),
|
||||
onPressed: () {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
// TODO: Implémenter la logique de connexion
|
||||
}
|
||||
},
|
||||
onPressed: _handleLogin,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
@@ -173,7 +267,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/register-choice');
|
||||
Navigator.pushNamed(
|
||||
context, '/register-choice');
|
||||
},
|
||||
child: Text(
|
||||
'Créer un compte',
|
||||
@@ -185,7 +280,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20), // Réduit l'espacement en bas
|
||||
const SizedBox(
|
||||
height: 20), // Réduit l'espacement en bas
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -251,12 +347,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
// Version mobile (à implémenter)
|
||||
return const Center(
|
||||
child: Text('Version mobile à implémenter'),
|
||||
);
|
||||
// return const Center(
|
||||
// child: Text('Version mobile à implémenter'),
|
||||
// );
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -298,14 +394,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (controller.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Veuillez décrire le problème',
|
||||
style: GoogleFonts.merienda(),
|
||||
),
|
||||
),
|
||||
);
|
||||
_showErrorSnackBar('Veuillez décrire le problème');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -313,25 +402,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||
await BugReportService.sendReport(controller.text);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Rapport envoyé avec succès',
|
||||
style: GoogleFonts.merienda(),
|
||||
),
|
||||
),
|
||||
);
|
||||
_showSuccessSnackBar('Rapport envoyé avec succès');
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Erreur lors de l\'envoi du rapport',
|
||||
style: GoogleFonts.merienda(),
|
||||
),
|
||||
),
|
||||
);
|
||||
_showErrorSnackBar('Erreur lors de l\'envoi du rapport');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -396,4 +471,4 @@ class _FooterLink extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
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 '../../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
|
||||
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
|
||||
|
||||
class ParentRegisterStep1Screen extends StatefulWidget {
|
||||
const ParentRegisterStep1Screen({super.key});
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
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 '../../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
|
||||
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
|
||||
|
||||
class ParentRegisterStep2Screen extends StatefulWidget {
|
||||
final UserRegistrationData registrationData; // Accepte les données de l'étape 1
|
||||
+6
-6
@@ -2,16 +2,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'dart:math' as math; // Pour la rotation du chevron
|
||||
import 'package:flutter/gestures.dart'; // Pour PointerDeviceKind
|
||||
import '../../widgets/hover_relief_widget.dart'; // Import du nouveau widget
|
||||
import '../../../widgets/hover_relief_widget.dart'; // Import du nouveau widget
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
// import 'package:image_cropper/image_cropper.dart'; // Supprimé
|
||||
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 '../../utils/data_generator.dart'; // Import du générateur
|
||||
import '../../models/card_assets.dart'; // Import des enums de cartes
|
||||
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/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
|
||||
|
||||
// La classe _ChildFormData est supprimée car on utilise ChildData du modèle
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ 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 '../../utils/data_generator.dart'; // Import du générateur
|
||||
import '../../models/card_assets.dart'; // Import des enums de cartes
|
||||
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
|
||||
|
||||
class ParentRegisterStep4Screen extends StatefulWidget {
|
||||
final UserRegistrationData registrationData; // Accepte les données
|
||||
+6
-5
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../models/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:p_tits_pas/widgets/Summary.dart';
|
||||
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;
|
||||
import '../../widgets/custom_decorated_text_field.dart'; // Import du CustomDecoratedTextField
|
||||
import '../../../widgets/custom_decorated_text_field.dart'; // Import du CustomDecoratedTextField
|
||||
|
||||
// Nouvelle méthode helper pour afficher un champ de type "lecture seule" stylisé
|
||||
Widget _buildDisplayFieldValue(BuildContext context, String label, String value, {bool multiLine = false, double fieldHeight = 50.0, double labelFontSize = 18.0}) {
|
||||
@@ -127,7 +128,7 @@ class ParentRegisterStep5Screen extends StatelessWidget {
|
||||
const SizedBox(height: verticalSpacing),
|
||||
_buildDisplayFieldValue(context, "Adresse:", "${data.address}\n${data.postalCode} ${data.city}".trim(), multiLine: true, fieldHeight: 80, labelFontSize: labelFontSize),
|
||||
];
|
||||
return _SummaryCard(
|
||||
return SummaryCard(
|
||||
backgroundImagePath: CardColorHorizontal.blue.path,
|
||||
title: 'Deuxième Parent',
|
||||
content: details,
|
||||
@@ -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');
|
||||
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user