Implémentation du parcours d'inscription des assistantes maternelles en 4 étapes + écran de confirmation, en utilisant Provider pour la gestion d'état. Fonctionnalités implémentées : - Étape 1 : Identité (nom, prénom, adresse, email, mot de passe) - Étape 2 : Infos professionnelles (photo, agrément, NIR, capacité d'accueil) - Étape 3 : Présentation personnelle et acceptation CGU - Étape 4 : Récapitulatif et validation finale - Écran de confirmation post-inscription Fichiers ajoutés : - models/nanny_registration_data.dart : Modèle de données avec Provider - screens/auth/nanny_register_step1_screen.dart : Identité - screens/auth/nanny_register_step2_screen.dart : Infos pro - screens/auth/nanny_register_step3_screen.dart : Présentation - screens/auth/nanny_register_step4_screen.dart : Récapitulatif - screens/auth/nanny_register_confirmation_screen.dart : Confirmation - screens/unknown_screen.dart : Écran pour routes inconnues - config/app_router.dart : Copie du routeur (à intégrer) Refs: #40 (Panneau 1 Identité), #41 (Panneau 2 Infos pro), #42 (Finalisation)
This commit is contained in:
parent
29bee9fa80
commit
105cf53e7b
123
frontend/lib/config/app_router.dart
Normal file
123
frontend/lib/config/app_router.dart
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
// Models
|
||||||
|
import '../models/user_registration_data.dart';
|
||||||
|
import '../models/nanny_registration_data.dart';
|
||||||
|
|
||||||
|
// Screens
|
||||||
|
import '../screens/auth/login_screen.dart';
|
||||||
|
import '../screens/auth/register_choice_screen.dart';
|
||||||
|
import '../screens/auth/parent_register_step1_screen.dart';
|
||||||
|
import '../screens/auth/parent_register_step2_screen.dart';
|
||||||
|
import '../screens/auth/parent_register_step3_screen.dart';
|
||||||
|
import '../screens/auth/parent_register_step4_screen.dart';
|
||||||
|
import '../screens/auth/parent_register_step5_screen.dart';
|
||||||
|
import '../screens/auth/nanny_register_step1_screen.dart';
|
||||||
|
import '../screens/auth/nanny_register_step2_screen.dart';
|
||||||
|
import '../screens/auth/nanny_register_step3_screen.dart';
|
||||||
|
import '../screens/auth/nanny_register_step4_screen.dart';
|
||||||
|
import '../screens/auth/nanny_register_confirmation_screen.dart';
|
||||||
|
import '../screens/home/home_screen.dart';
|
||||||
|
import '../screens/unknown_screen.dart';
|
||||||
|
|
||||||
|
// --- Provider Instances ---
|
||||||
|
// It's generally better to provide these higher up the widget tree if possible,
|
||||||
|
// or ensure they are created only once.
|
||||||
|
// For ShellRoute, creating them here and passing via .value is common.
|
||||||
|
|
||||||
|
final userRegistrationDataNotifier = UserRegistrationData();
|
||||||
|
final nannyRegistrationDataNotifier = NannyRegistrationData();
|
||||||
|
|
||||||
|
class AppRouter {
|
||||||
|
static final GoRouter router = GoRouter(
|
||||||
|
initialLocation: '/login',
|
||||||
|
errorBuilder: (context, state) => const UnknownScreen(),
|
||||||
|
debugLogDiagnostics: true,
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/login',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const LoginScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/register-choice',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const RegisterChoiceScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/home',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const HomeScreen(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// --- Parent Registration Flow ---
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ChangeNotifierProvider<UserRegistrationData>.value(
|
||||||
|
value: userRegistrationDataNotifier,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-step1',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const ParentRegisterStep1Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-step2',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const ParentRegisterStep2Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-step3',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const ParentRegisterStep3Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-step4',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const ParentRegisterStep4Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-step5',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const ParentRegisterStep5Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/parent-register-confirmation',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const NannyRegisterConfirmationScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// --- Nanny Registration Flow ---
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ChangeNotifierProvider<NannyRegistrationData>.value(
|
||||||
|
value: nannyRegistrationDataNotifier,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/nanny-register-step1',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const NannyRegisterStep1Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/nanny-register-step2',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const NannyRegisterStep2Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/nanny-register-step3',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const NannyRegisterStep3Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/nanny-register-step4',
|
||||||
|
builder: (BuildContext context, GoRouterState state) => const NannyRegisterStep4Screen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/nanny-register-confirmation',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
return const NannyRegisterConfirmationScreen();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
136
frontend/lib/models/nanny_registration_data.dart
Normal file
136
frontend/lib/models/nanny_registration_data.dart
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class NannyRegistrationData extends ChangeNotifier {
|
||||||
|
// Step 1: Identity Info
|
||||||
|
String firstName = '';
|
||||||
|
String lastName = '';
|
||||||
|
String streetAddress = ''; // Nouveau pour N° et Rue
|
||||||
|
String postalCode = ''; // Nouveau
|
||||||
|
String city = ''; // Nouveau
|
||||||
|
String phone = '';
|
||||||
|
String email = '';
|
||||||
|
String password = '';
|
||||||
|
// String? photoPath; // Déplacé ou géré à l'étape 2
|
||||||
|
// bool photoConsent = false; // Déplacé ou géré à l'étape 2
|
||||||
|
|
||||||
|
// Step 2: Professional Info
|
||||||
|
String? photoPath; // Ajouté pour l'étape 2
|
||||||
|
bool photoConsent = false; // Ajouté pour l'étape 2
|
||||||
|
DateTime? dateOfBirth;
|
||||||
|
String birthCity = ''; // Nouveau
|
||||||
|
String birthCountry = ''; // Nouveau
|
||||||
|
// String placeOfBirth = ''; // Remplacé par birthCity et birthCountry
|
||||||
|
String nir = ''; // Numéro de Sécurité Sociale
|
||||||
|
String agrementNumber = ''; // Numéro d'agrément
|
||||||
|
int? capacity; // Number of children the nanny can look after
|
||||||
|
|
||||||
|
// Step 3: Presentation & CGU
|
||||||
|
String presentationText = '';
|
||||||
|
bool cguAccepted = false;
|
||||||
|
|
||||||
|
// --- Methods to update data and notify listeners ---
|
||||||
|
|
||||||
|
void updateIdentityInfo({
|
||||||
|
String? firstName,
|
||||||
|
String? lastName,
|
||||||
|
String? streetAddress, // Modifié
|
||||||
|
String? postalCode, // Nouveau
|
||||||
|
String? city, // Nouveau
|
||||||
|
String? phone,
|
||||||
|
String? email,
|
||||||
|
String? password,
|
||||||
|
}) {
|
||||||
|
this.firstName = firstName ?? this.firstName;
|
||||||
|
this.lastName = lastName ?? this.lastName;
|
||||||
|
this.streetAddress = streetAddress ?? this.streetAddress; // Modifié
|
||||||
|
this.postalCode = postalCode ?? this.postalCode; // Nouveau
|
||||||
|
this.city = city ?? this.city; // Nouveau
|
||||||
|
this.phone = phone ?? this.phone;
|
||||||
|
this.email = email ?? this.email;
|
||||||
|
this.password = password ?? this.password;
|
||||||
|
// if (photoPath != null || this.photoPath != null) { // Supprimé de l'étape 1
|
||||||
|
// this.photoPath = photoPath;
|
||||||
|
// }
|
||||||
|
// this.photoConsent = photoConsent ?? this.photoConsent; // Supprimé de l'étape 1
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateProfessionalInfo({
|
||||||
|
String? photoPath,
|
||||||
|
bool? photoConsent,
|
||||||
|
DateTime? dateOfBirth,
|
||||||
|
String? birthCity, // Nouveau
|
||||||
|
String? birthCountry, // Nouveau
|
||||||
|
// String? placeOfBirth, // Remplacé
|
||||||
|
String? nir,
|
||||||
|
String? agrementNumber,
|
||||||
|
int? capacity,
|
||||||
|
}) {
|
||||||
|
// Allow setting photoPath to null explicitly
|
||||||
|
if (photoPath != null || this.photoPath != null) {
|
||||||
|
this.photoPath = photoPath;
|
||||||
|
}
|
||||||
|
this.photoConsent = photoConsent ?? this.photoConsent;
|
||||||
|
this.dateOfBirth = dateOfBirth ?? this.dateOfBirth;
|
||||||
|
this.birthCity = birthCity ?? this.birthCity; // Nouveau
|
||||||
|
this.birthCountry = birthCountry ?? this.birthCountry; // Nouveau
|
||||||
|
// this.placeOfBirth = placeOfBirth ?? this.placeOfBirth; // Remplacé
|
||||||
|
this.nir = nir ?? this.nir;
|
||||||
|
this.agrementNumber = agrementNumber ?? this.agrementNumber;
|
||||||
|
this.capacity = capacity ?? this.capacity;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePresentationAndCgu({
|
||||||
|
String? presentationText,
|
||||||
|
bool? cguAccepted,
|
||||||
|
}) {
|
||||||
|
this.presentationText = presentationText ?? this.presentationText;
|
||||||
|
this.cguAccepted = cguAccepted ?? this.cguAccepted;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Getters for validation or display ---
|
||||||
|
bool get isStep1Complete =>
|
||||||
|
firstName.isNotEmpty &&
|
||||||
|
lastName.isNotEmpty &&
|
||||||
|
streetAddress.isNotEmpty && // Modifié
|
||||||
|
postalCode.isNotEmpty && // Nouveau
|
||||||
|
city.isNotEmpty && // Nouveau
|
||||||
|
phone.isNotEmpty &&
|
||||||
|
email.isNotEmpty &&
|
||||||
|
password.isNotEmpty;
|
||||||
|
|
||||||
|
bool get isStep2Complete =>
|
||||||
|
// photoConsent is mandatory if a photo is system-required, otherwise optional.
|
||||||
|
// For now, let's assume if photoPath is present, consent should ideally be true.
|
||||||
|
// Or, make consent always mandatory if photo section exists.
|
||||||
|
// Based on new mockup, photo is present, so consent might be implicitly or explicitly needed.
|
||||||
|
(photoPath != null ? photoConsent == true : true) && // Ajuster selon la logique de consentement désirée
|
||||||
|
dateOfBirth != null &&
|
||||||
|
birthCity.isNotEmpty &&
|
||||||
|
birthCountry.isNotEmpty &&
|
||||||
|
nir.isNotEmpty && // Basic check, could add validation
|
||||||
|
agrementNumber.isNotEmpty &&
|
||||||
|
capacity != null && capacity! > 0;
|
||||||
|
|
||||||
|
bool get isStep3Complete =>
|
||||||
|
// presentationText is optional as per CDC (message au gestionnaire)
|
||||||
|
cguAccepted;
|
||||||
|
|
||||||
|
bool get isRegistrationComplete =>
|
||||||
|
isStep1Complete && isStep2Complete && isStep3Complete;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NannyRegistrationData('
|
||||||
|
'firstName: $firstName, lastName: $lastName, '
|
||||||
|
'streetAddress: $streetAddress, postalCode: $postalCode, city: $city, '
|
||||||
|
'phone: $phone, email: $email, '
|
||||||
|
// 'photoPath: $photoPath, photoConsent: $photoConsent, ' // Commenté car déplacé/modifié
|
||||||
|
'dateOfBirth: $dateOfBirth, birthCity: $birthCity, birthCountry: $birthCountry, '
|
||||||
|
'nir: $nir, agrementNumber: $agrementNumber, capacity: $capacity, '
|
||||||
|
'photoPath (step2): $photoPath, photoConsent (step2): $photoConsent, '
|
||||||
|
'presentationText: $presentationText, cguAccepted: $cguAccepted)';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class NannyRegisterConfirmationScreen extends StatelessWidget {
|
||||||
|
const NannyRegisterConfirmationScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Inscription Soumise'),
|
||||||
|
automaticallyImplyLeading: false, // Remove back button
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle_outline, color: Colors.green, size: 80),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text(
|
||||||
|
'Votre demande d\'inscription a été soumise avec succès !',
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
const Text(
|
||||||
|
'Votre compte est en attente de validation par un gestionnaire. Vous recevrez une notification par e-mail une fois votre compte activé.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// Navigate back to the login screen
|
||||||
|
context.go('/login');
|
||||||
|
},
|
||||||
|
child: const Text('Retour à la connexion'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
239
frontend/lib/screens/auth/nanny_register_step1_screen.dart
Normal file
239
frontend/lib/screens/auth/nanny_register_step1_screen.dart
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:math' as math; // Pour la rotation du chevron
|
||||||
|
|
||||||
|
import '../../../models/nanny_registration_data.dart';
|
||||||
|
import '../../../widgets/custom_app_text_field.dart';
|
||||||
|
import '../../../models/card_assets.dart'; // Pour les cartes
|
||||||
|
import '../../../utils/data_generator.dart'; // Implied import for DataGenerator
|
||||||
|
|
||||||
|
class NannyRegisterStep1Screen extends StatefulWidget {
|
||||||
|
const NannyRegisterStep1Screen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NannyRegisterStep1Screen> createState() => _NannyRegisterStep1ScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NannyRegisterStep1ScreenState extends State<NannyRegisterStep1Screen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
final _firstNameController = TextEditingController();
|
||||||
|
final _lastNameController = TextEditingController();
|
||||||
|
final _streetAddressController = TextEditingController();
|
||||||
|
final _postalCodeController = TextEditingController();
|
||||||
|
final _cityController = TextEditingController();
|
||||||
|
final _phoneController = TextEditingController();
|
||||||
|
final _emailController = TextEditingController();
|
||||||
|
final _passwordController = TextEditingController();
|
||||||
|
final _confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final data = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
|
||||||
|
_firstNameController.text = data.firstName;
|
||||||
|
_lastNameController.text = data.lastName;
|
||||||
|
_streetAddressController.text = data.streetAddress;
|
||||||
|
_postalCodeController.text = data.postalCode;
|
||||||
|
_cityController.text = data.city;
|
||||||
|
_phoneController.text = data.phone;
|
||||||
|
_emailController.text = data.email;
|
||||||
|
_passwordController.text = data.password;
|
||||||
|
_confirmPasswordController.text = data.password.isNotEmpty ? data.password : '';
|
||||||
|
|
||||||
|
if (data.firstName.isEmpty && data.lastName.isEmpty) {
|
||||||
|
final String genFirstName = DataGenerator.firstName();
|
||||||
|
final String genLastName = DataGenerator.lastName();
|
||||||
|
_firstNameController.text = genFirstName;
|
||||||
|
_lastNameController.text = genLastName;
|
||||||
|
_streetAddressController.text = DataGenerator.address();
|
||||||
|
_postalCodeController.text = DataGenerator.postalCode();
|
||||||
|
_cityController.text = DataGenerator.city();
|
||||||
|
_phoneController.text = DataGenerator.phone();
|
||||||
|
_emailController.text = DataGenerator.email(genFirstName, genLastName);
|
||||||
|
_passwordController.text = DataGenerator.password();
|
||||||
|
_confirmPasswordController.text = _passwordController.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_firstNameController.dispose();
|
||||||
|
_lastNameController.dispose();
|
||||||
|
_streetAddressController.dispose();
|
||||||
|
_postalCodeController.dispose();
|
||||||
|
_cityController.dispose();
|
||||||
|
_phoneController.dispose();
|
||||||
|
_emailController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
_confirmPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submitForm() {
|
||||||
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
|
Provider.of<NannyRegistrationData>(context, listen: false)
|
||||||
|
.updateIdentityInfo(
|
||||||
|
firstName: _firstNameController.text,
|
||||||
|
lastName: _lastNameController.text,
|
||||||
|
streetAddress: _streetAddressController.text,
|
||||||
|
postalCode: _postalCodeController.text,
|
||||||
|
city: _cityController.text,
|
||||||
|
phone: _phoneController.text,
|
||||||
|
email: _emailController.text,
|
||||||
|
password: _passwordController.text,
|
||||||
|
);
|
||||||
|
context.go('/nanny-register-step2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenSize = MediaQuery.of(context).size;
|
||||||
|
const cardColor = CardColorHorizontal.blue;
|
||||||
|
|
||||||
|
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),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Étape 1/4',
|
||||||
|
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
width: screenSize.width * 0.7,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
||||||
|
constraints: const BoxConstraints(minHeight: 600),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage(cardColor.path),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Vos informations personnelles',
|
||||||
|
style: GoogleFonts.merienda(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Votre nom', fieldWidth: double.infinity)),
|
||||||
|
Expanded(flex: 1, child: const SizedBox()),
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Votre prénom', fieldWidth: double.infinity)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', hintText: 'Votre téléphone', keyboardType: TextInputType.phone, fieldWidth: double.infinity)),
|
||||||
|
Expanded(flex: 1, child: const SizedBox()),
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', hintText: 'Votre e-mail', keyboardType: TextInputType.emailAddress, fieldWidth: double.infinity)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _passwordController, labelText: 'Mot de passe', hintText: 'Minimum 6 caractères', obscureText: true, fieldWidth: double.infinity,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) return 'Mot de passe requis';
|
||||||
|
if (value.length < 6) return '6 caractères minimum';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
Expanded(flex: 1, child: const SizedBox()),
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _confirmPasswordController, labelText: 'Confirmation', hintText: 'Confirmez le mot de passe', obscureText: true, fieldWidth: double.infinity,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) return 'Confirmation requise';
|
||||||
|
if (value != _passwordController.text) return 'Les mots de passe ne correspondent pas';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CustomAppTextField(
|
||||||
|
controller: _streetAddressController,
|
||||||
|
labelText: 'Adresse (N° et Rue)',
|
||||||
|
hintText: 'Numéro et nom de votre rue',
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) => v!.isEmpty ? 'Adresse requise' : null,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 5, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', hintText: 'C.P.', keyboardType: TextInputType.number, fieldWidth: double.infinity, validator: (v) => v!.isEmpty ? 'C.P. requis' : null)),
|
||||||
|
Expanded(flex: 1, child: const SizedBox()),
|
||||||
|
Expanded(flex: 12, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Votre ville', fieldWidth: double.infinity, validator: (v) => v!.isEmpty ? 'Ville requise' : null)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
} else {
|
||||||
|
context.go('/register-choice');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: 'Retour',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: screenSize.height / 2 - 20,
|
||||||
|
right: 40,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
|
onPressed: _submitForm,
|
||||||
|
tooltip: 'Suivant',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
339
frontend/lib/screens/auth/nanny_register_step2_screen.dart
Normal file
339
frontend/lib/screens/auth/nanny_register_step2_screen.dart
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:io'; // Pour FileImage si _pickPhoto utilise un File
|
||||||
|
|
||||||
|
import '../../../models/nanny_registration_data.dart';
|
||||||
|
import '../../../widgets/custom_app_text_field.dart';
|
||||||
|
import '../../../widgets/app_custom_checkbox.dart'; // Import de la checkbox
|
||||||
|
import '../../../widgets/hover_relief_widget.dart'; // Import du HoverReliefWidget
|
||||||
|
import '../../../models/card_assets.dart';
|
||||||
|
// import '../../../utils/data_generator.dart'; // Plus besoin pour l'initialisation directe ici
|
||||||
|
|
||||||
|
class NannyRegisterStep2Screen extends StatefulWidget {
|
||||||
|
const NannyRegisterStep2Screen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NannyRegisterStep2Screen> createState() => _NannyRegisterStep2ScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NannyRegisterStep2ScreenState extends State<NannyRegisterStep2Screen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
final _dateOfBirthController = TextEditingController();
|
||||||
|
// final _placeOfBirthController = TextEditingController(); // Remplacé
|
||||||
|
final _birthCityController = TextEditingController(); // Nouveau
|
||||||
|
final _birthCountryController = TextEditingController(); // Nouveau
|
||||||
|
final _nirController = TextEditingController();
|
||||||
|
final _agrementController = TextEditingController();
|
||||||
|
final _capacityController = TextEditingController();
|
||||||
|
DateTime? _selectedDate;
|
||||||
|
String? _photoPathFramework; // Pour stocker le chemin de la photo (Asset ou File path)
|
||||||
|
File? _photoFile; // Pour stocker le fichier image si sélectionné localement
|
||||||
|
bool _photoConsent = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final data = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
_selectedDate = data.dateOfBirth;
|
||||||
|
_dateOfBirthController.text = data.dateOfBirth != null ? DateFormat('dd/MM/yyyy').format(data.dateOfBirth!) : '';
|
||||||
|
_birthCityController.text = data.birthCity;
|
||||||
|
_birthCountryController.text = data.birthCountry;
|
||||||
|
_nirController.text = data.nir;
|
||||||
|
_agrementController.text = data.agrementNumber;
|
||||||
|
_capacityController.text = data.capacity?.toString() ?? '';
|
||||||
|
// Gérer la photo existante (pourrait être un path d'asset ou un path de fichier)
|
||||||
|
if (data.photoPath != null) {
|
||||||
|
if (data.photoPath!.startsWith('assets/')) {
|
||||||
|
_photoPathFramework = data.photoPath;
|
||||||
|
_photoFile = null;
|
||||||
|
} else {
|
||||||
|
_photoFile = File(data.photoPath!);
|
||||||
|
_photoPathFramework = data.photoPath; // ou _photoFile.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_photoConsent = data.photoConsent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_dateOfBirthController.dispose();
|
||||||
|
_birthCityController.dispose();
|
||||||
|
_birthCountryController.dispose();
|
||||||
|
_nirController.dispose();
|
||||||
|
_agrementController.dispose();
|
||||||
|
_capacityController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectDate(BuildContext context) async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _selectedDate ?? DateTime.now().subtract(const Duration(days: 365 * 25)), // Default à 25 ans si null
|
||||||
|
firstDate: DateTime(1920, 1),
|
||||||
|
lastDate: DateTime.now().subtract(const Duration(days: 365 * 18)), // Assurer un âge minimum de 18 ans
|
||||||
|
locale: const Locale('fr', 'FR'),
|
||||||
|
);
|
||||||
|
if (picked != null && picked != _selectedDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedDate = picked;
|
||||||
|
_dateOfBirthController.text = DateFormat('dd/MM/yyyy').format(picked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickPhoto() async {
|
||||||
|
// TODO: Remplacer par la vraie logique ImagePicker
|
||||||
|
// final imagePicker = ImagePicker();
|
||||||
|
// final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery);
|
||||||
|
// if (pickedFile != null) {
|
||||||
|
// setState(() {
|
||||||
|
// _photoFile = File(pickedFile.path);
|
||||||
|
// _photoPathFramework = pickedFile.path; // pour la sauvegarde
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// // Simuler la sélection d'un asset pour test si aucun fichier n'est choisi
|
||||||
|
setState(() {
|
||||||
|
_photoPathFramework = 'assets/images/icon_assmat.png'; // Simule une photo asset
|
||||||
|
_photoFile = null; // Assurez-vous que _photoFile est null si c'est un asset
|
||||||
|
});
|
||||||
|
// }
|
||||||
|
print("Photo sélectionnée: $_photoPathFramework");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submitForm() {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
if (_photoPathFramework != null && !_photoConsent) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider.of<NannyRegistrationData>(context, listen: false)
|
||||||
|
.updateProfessionalInfo(
|
||||||
|
photoPath: _photoPathFramework, // Sauvegarder le chemin (asset ou fichier)
|
||||||
|
photoConsent: _photoConsent,
|
||||||
|
dateOfBirth: _selectedDate,
|
||||||
|
birthCity: _birthCityController.text,
|
||||||
|
birthCountry: _birthCountryController.text,
|
||||||
|
nir: _nirController.text,
|
||||||
|
agrementNumber: _agrementController.text,
|
||||||
|
capacity: int.tryParse(_capacityController.text)
|
||||||
|
);
|
||||||
|
context.go('/nanny-register-step3');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenSize = MediaQuery.of(context).size;
|
||||||
|
const cardColor = CardColorHorizontal.green; // Couleur de la carte
|
||||||
|
final Color baseCardColorForShadow = Colors.green.shade300;
|
||||||
|
final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90);
|
||||||
|
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
||||||
|
|
||||||
|
ImageProvider? currentImageProvider;
|
||||||
|
if (_photoFile != null) {
|
||||||
|
currentImageProvider = FileImage(_photoFile!);
|
||||||
|
} else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) {
|
||||||
|
currentImageProvider = AssetImage(_photoPathFramework!);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Étape 2/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
width: screenSize.width * 0.7, // Largeur de la carte
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
||||||
|
constraints: const BoxConstraints(minHeight: 650), // Hauteur minimale ajustée
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(image: AssetImage(cardColor.path), fit: BoxFit.fill),
|
||||||
|
),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Vos informations professionnelles',
|
||||||
|
style: GoogleFonts.merienda(
|
||||||
|
fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, // Couleur du titre ajustée
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Colonne Gauche: Photo et Checkbox
|
||||||
|
SizedBox(
|
||||||
|
width: 300, // Largeur fixe pour la colonne photo (200 * 1.5)
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, // Centrer les éléments horizontalement
|
||||||
|
children: [
|
||||||
|
HoverReliefWidget(
|
||||||
|
onPressed: _pickPhoto,
|
||||||
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
|
initialShadowColor: initialPhotoShadow,
|
||||||
|
hoverShadowColor: hoverPhotoShadow,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 270, // (180 * 1.5)
|
||||||
|
width: 270, // (180 * 1.5)
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
image: currentImageProvider != null
|
||||||
|
? DecorationImage(image: currentImageProvider, fit: BoxFit.cover)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: currentImageProvider == null
|
||||||
|
? Image.asset('assets/images/photo.png', fit: BoxFit.contain)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espace réduit
|
||||||
|
AppCustomCheckbox(
|
||||||
|
label: 'J\'accepte l\'utilisation de ma photo.',
|
||||||
|
value: _photoConsent,
|
||||||
|
onChanged: (val) => setState(() => _photoConsent = val ?? false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 30), // Augmenter l'espace entre les colonnes
|
||||||
|
// Colonne Droite: Champs de naissance
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CustomAppTextField(
|
||||||
|
controller: _birthCityController,
|
||||||
|
labelText: 'Ville de naissance',
|
||||||
|
hintText: 'Votre ville de naissance',
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) => v!.isEmpty ? 'Ville requise' : null,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CustomAppTextField(
|
||||||
|
controller: _birthCountryController,
|
||||||
|
labelText: 'Pays de naissance',
|
||||||
|
hintText: 'Votre pays de naissance',
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) => v!.isEmpty ? 'Pays requis' : null,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CustomAppTextField(
|
||||||
|
controller: _dateOfBirthController,
|
||||||
|
labelText: 'Date de naissance',
|
||||||
|
hintText: 'JJ/MM/AAAA',
|
||||||
|
readOnly: true,
|
||||||
|
onTap: () => _selectDate(context),
|
||||||
|
suffixIcon: Icons.calendar_today, // Assurez-vous que CustomAppTextField gère suffixIcon
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) => _selectedDate == null ? 'Date requise' : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CustomAppTextField(
|
||||||
|
controller: _nirController,
|
||||||
|
labelText: 'N° Sécurité Sociale (NIR)',
|
||||||
|
hintText: 'Votre NIR à 13 chiffres',
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) { // Validation plus précise du NIR
|
||||||
|
if (v == null || v.isEmpty) return 'NIR requis';
|
||||||
|
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
||||||
|
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
||||||
|
// D'autres validations plus complexes (clé de contrôle) peuvent être ajoutées
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomAppTextField(
|
||||||
|
controller: _agrementController,
|
||||||
|
labelText: 'N° d\'agrément',
|
||||||
|
hintText: 'Votre numéro d\'agrément',
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) => v!.isEmpty ? 'Agrément requis' : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: CustomAppTextField(
|
||||||
|
controller: _capacityController,
|
||||||
|
labelText: 'Capacité d\'accueil',
|
||||||
|
hintText: 'Ex: 3',
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
fieldWidth: double.infinity,
|
||||||
|
validator: (v) {
|
||||||
|
if (v == null || v.isEmpty) return 'Capacité requise';
|
||||||
|
final n = int.tryParse(v);
|
||||||
|
if (n == null || n <= 0) return 'Nombre invalide';
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Chevron Gauche (Retour)
|
||||||
|
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: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
} else {
|
||||||
|
context.go('/nanny-register-step1');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: 'Précédent',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Chevron Droit (Suivant)
|
||||||
|
Positioned(
|
||||||
|
top: screenSize.height / 2 - 20,
|
||||||
|
right: 40,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
|
onPressed: _submitForm,
|
||||||
|
tooltip: 'Suivant',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
145
frontend/lib/screens/auth/nanny_register_step3_screen.dart
Normal file
145
frontend/lib/screens/auth/nanny_register_step3_screen.dart
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import '../../../models/nanny_registration_data.dart';
|
||||||
|
import '../../../widgets/custom_decorated_text_field.dart';
|
||||||
|
import '../../../models/card_assets.dart';
|
||||||
|
|
||||||
|
class NannyRegisterStep3Screen extends StatefulWidget {
|
||||||
|
const NannyRegisterStep3Screen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NannyRegisterStep3Screen> createState() => _NannyRegisterStep3ScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NannyRegisterStep3ScreenState extends State<NannyRegisterStep3Screen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _presentationController = TextEditingController();
|
||||||
|
bool _cguAccepted = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final data = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
_presentationController.text = 'Disponible immédiatement, expérience avec les tout-petits.';
|
||||||
|
_cguAccepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_presentationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submitForm() {
|
||||||
|
final nannyData = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
nannyData.updatePresentationAndCgu(presentationText: _presentationController.text);
|
||||||
|
// Validation CGU désactivée temporairement
|
||||||
|
nannyData.updatePresentationAndCgu(cguAccepted: _cguAccepted);
|
||||||
|
context.go('/nanny-register-step4');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final nannyData = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
final screenSize = MediaQuery.of(context).size;
|
||||||
|
const cardColor = CardColorHorizontal.peach; // Couleur différente
|
||||||
|
|
||||||
|
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),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Étape 3/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
width: screenSize.width * 0.7,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
||||||
|
constraints: const BoxConstraints(minHeight: 500), // Ajuster hauteur
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(image: AssetImage(cardColor.path), fit: BoxFit.fill),
|
||||||
|
),
|
||||||
|
child: Form( // Garder Form même si validation simple
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Présentation et Conditions',
|
||||||
|
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
'Rédigez un court message à destination du gestionnaire (facultatif) :',
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.black87),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
CustomDecoratedTextField(
|
||||||
|
controller: _presentationController,
|
||||||
|
hintText: 'Ex: Disponible immédiatement, formation premiers secours...',
|
||||||
|
maxLines: 6,
|
||||||
|
// style: cardColor.textFieldStyle, // Utiliser style par défaut ou adapter
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('J\'ai lu et j\'accepte les Conditions Générales d\'Utilisation et la Politique de confidentialité de P\'titsPas.', style: TextStyle(fontSize: 14)),
|
||||||
|
subtitle: Text('Vous devez accepter pour continuer.', style: TextStyle(color: Colors.black54.withOpacity(0.7))),
|
||||||
|
value: _cguAccepted,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
setState(() { _cguAccepted = value ?? false; });
|
||||||
|
},
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
dense: true,
|
||||||
|
activeColor: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
// TODO: Ajouter lien vers CGU
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Chevron Gauche (Retour)
|
||||||
|
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: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
} else {
|
||||||
|
context.go('/nanny-register-step2');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: 'Précédent',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Chevron Droit (Suivant)
|
||||||
|
Positioned(
|
||||||
|
top: screenSize.height / 2 - 20,
|
||||||
|
right: 40,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
|
onPressed: _submitForm,
|
||||||
|
tooltip: 'Suivant',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
251
frontend/lib/screens/auth/nanny_register_step4_screen.dart
Normal file
251
frontend/lib/screens/auth/nanny_register_step4_screen.dart
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
// import 'package:p_tits_pas/utils/resources/card_color_horizontal.dart'; // Supprimé car incorrect
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:p_tits_pas/models/card_assets.dart';
|
||||||
|
import '../../../models/nanny_registration_data.dart';
|
||||||
|
// import '../../../widgets/registration_scaffold.dart'; // Widget inexistant
|
||||||
|
// import '../../../widgets/recap_card.dart'; // Widget inexistant
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
class NannyRegisterStep4Screen extends StatelessWidget {
|
||||||
|
const NannyRegisterStep4Screen({super.key});
|
||||||
|
|
||||||
|
void _submitRegistration(BuildContext context) {
|
||||||
|
final nannyData = Provider.of<NannyRegistrationData>(context, listen: false);
|
||||||
|
print('Submitting Nanny Registration: ${nannyData.toString()}');
|
||||||
|
// TODO: Implement actual submission logic (e.g., API call)
|
||||||
|
context.go('/nanny-register-confirmation');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final nannyData = Provider.of<NannyRegistrationData>(context);
|
||||||
|
final dateFormat = DateFormat('dd/MM/yyyy');
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final bool canSubmit = nannyData.isRegistrationComplete; // Check completeness
|
||||||
|
|
||||||
|
return Scaffold( // Main scaffold to contain the stack
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// Background image
|
||||||
|
Positioned.fill(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/paper2.png', // Assurez-vous que le chemin est correct
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Content centered
|
||||||
|
Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: size.width * 0.8, // Adjust width as needed
|
||||||
|
maxHeight: size.height * 0.85, // Adjust height as needed
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage(CardColorHorizontal.blue.path),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 40.0, bottom: 10.0),
|
||||||
|
child: Text(
|
||||||
|
'Récapitulatif',
|
||||||
|
style: GoogleFonts.merienda(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Veuillez vérifier attentivement les informations avant de soumettre.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: Colors.white70),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// --- Identity Card (Using standard Card for grouping) ---
|
||||||
|
Card(
|
||||||
|
elevation: 2.0,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(15.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildCardTitle(context, 'Informations personnelles', '/nanny-register-step1'),
|
||||||
|
const Divider(),
|
||||||
|
if (nannyData.photoPath != null && nannyData.photoPath!.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 40,
|
||||||
|
backgroundImage: FileImage(File(nannyData.photoPath!)),
|
||||||
|
onBackgroundImageError: (exception, stackTrace) {
|
||||||
|
print("Erreur chargement image: $exception");
|
||||||
|
// Optionnel: afficher un placeholder ou icône d'erreur
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildRecapRow('Nom:', '${nannyData.firstName} ${nannyData.lastName}'),
|
||||||
|
_buildRecapRow('Adresse:', '${nannyData.streetAddress}${nannyData.postalCode.isNotEmpty ? '\n${nannyData.postalCode}' : ''}${nannyData.city.isNotEmpty ? ' ${nannyData.city}' : ''}'.trim()),
|
||||||
|
_buildRecapRow('Téléphone:', nannyData.phone),
|
||||||
|
_buildRecapRow('Email:', nannyData.email),
|
||||||
|
_buildRecapRow('Consentement Photo:', nannyData.photoConsent ? 'Oui' : 'Non'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// --- Professional Info Card (Using standard Card) ---
|
||||||
|
Card(
|
||||||
|
elevation: 2.0,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(15.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildCardTitle(context, 'Informations professionnelles', '/nanny-register-step2'),
|
||||||
|
const Divider(),
|
||||||
|
_buildRecapRow('Date de naissance:', nannyData.dateOfBirth != null ? dateFormat.format(nannyData.dateOfBirth!) : 'Non renseigné'),
|
||||||
|
_buildRecapRow('Lieu de naissance:', '${nannyData.birthCity}, ${nannyData.birthCountry}'.isNotEmpty ? '${nannyData.birthCity}, ${nannyData.birthCountry}' : 'Non renseigné'),
|
||||||
|
_buildRecapRow('N° Sécurité Sociale:', nannyData.nir.isNotEmpty ? nannyData.nir : 'Non renseigné'), // TODO: Mask this?
|
||||||
|
_buildRecapRow('N° Agrément:', nannyData.agrementNumber.isNotEmpty ? nannyData.agrementNumber : 'Non renseigné'),
|
||||||
|
_buildRecapRow('Capacité d\'accueil:', nannyData.capacity?.toString() ?? 'Non renseigné'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// --- Presentation Card (Using standard Card) ---
|
||||||
|
Card(
|
||||||
|
elevation: 2.0,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(15.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildCardTitle(context, 'Présentation & CGU', '/nanny-register-step3'),
|
||||||
|
const Divider(),
|
||||||
|
const Text('Votre présentation (facultatif) :', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: Colors.grey[300]!)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
nannyData.presentationText.isNotEmpty
|
||||||
|
? nannyData.presentationText
|
||||||
|
: 'Aucune présentation rédigée.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: nannyData.presentationText.isNotEmpty ? Colors.black87 : Colors.grey,
|
||||||
|
fontStyle: nannyData.presentationText.isNotEmpty ? FontStyle.normal : FontStyle.italic
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
_buildRecapRow('CGU Acceptées:', nannyData.cguAccepted ? 'Oui' : 'Non'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!canSubmit) // Show warning if incomplete
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 15.0, bottom: 5.0), // Add some space
|
||||||
|
child: Text(
|
||||||
|
'Veuillez compléter toutes les étapes requises et accepter les CGU pour pouvoir soumettre.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Navigation buttons
|
||||||
|
Positioned(
|
||||||
|
top: size.height / 2 - 20, // Centré verticalement
|
||||||
|
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: () => context.go('/nanny-register-step3'),
|
||||||
|
tooltip: 'Précédent',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: size.height / 2 - 20, // Centré verticalement
|
||||||
|
right: 40,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
|
onPressed: canSubmit ? () => _submitRegistration(context) : null,
|
||||||
|
tooltip: 'Soumettre',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to build title row with edit button
|
||||||
|
Widget _buildCardTitle(BuildContext context, String title, String editRoute) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit, size: 20),
|
||||||
|
onPressed: () => context.go(editRoute),
|
||||||
|
tooltip: 'Modifier',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to build data row
|
||||||
|
Widget _buildRecapRow(String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('$label ', style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
Expanded(child: Text(value)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
frontend/lib/screens/unknown_screen.dart
Normal file
15
frontend/lib/screens/unknown_screen.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class UnknownScreen extends StatelessWidget {
|
||||||
|
const UnknownScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Page Introuvable')),
|
||||||
|
body: const Center(
|
||||||
|
child: Text('Désolé, cette page n\'existe pas.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user