- ParentRegistrationPayload : validation client et mapping UserRegistrationData → JSON API - AuthService.registerParent - Étape 5 : appel API, chargement, erreurs en dialogue, succès inchangé puis /login Made-with: Cursor
215 lines
6.9 KiB
Dart
215 lines
6.9 KiB
Dart
import 'dart:convert';
|
||
|
||
import '../models/user_registration_data.dart';
|
||
|
||
/// Construction du body `POST /auth/register/parent` à partir du state d'inscription.
|
||
/// Aligné sur [RegisterParentCompletDto] (backend).
|
||
class ParentRegistrationPayload {
|
||
ParentRegistrationPayload._();
|
||
|
||
static final RegExp _phoneFr = RegExp(r'^(\+33|0)[1-9](\d{2}){4}$');
|
||
|
||
/// Retourne un message d'erreur utilisateur, ou `null` si le formulaire est cohérent.
|
||
static String? validateForApi(UserRegistrationData d) {
|
||
final p1 = d.parent1;
|
||
if (p1.email.trim().isEmpty) {
|
||
return 'L’email du parent principal est requis.';
|
||
}
|
||
if (p1.firstName.trim().length < 2) {
|
||
return 'Le prénom du parent principal doit contenir au moins 2 caractères.';
|
||
}
|
||
if (p1.lastName.trim().length < 2) {
|
||
return 'Le nom du parent principal doit contenir au moins 2 caractères.';
|
||
}
|
||
final tel = _normalizePhone(p1.phone);
|
||
if (!_phoneFr.hasMatch(tel)) {
|
||
return 'Le numéro de téléphone du parent principal n’est pas valide (ex. 0612345678).';
|
||
}
|
||
if (!d.cguAccepted) {
|
||
return 'Vous devez accepter les CGU et la politique de confidentialité.';
|
||
}
|
||
if (d.children.isEmpty) {
|
||
return 'Au moins un enfant est requis.';
|
||
}
|
||
|
||
final p2 = d.parent2;
|
||
if (p2 != null) {
|
||
final any = p2.email.trim().isNotEmpty ||
|
||
p2.firstName.trim().isNotEmpty ||
|
||
p2.lastName.trim().isNotEmpty ||
|
||
p2.phone.trim().isNotEmpty;
|
||
if (any) {
|
||
if (p2.email.trim().isEmpty ||
|
||
p2.firstName.trim().length < 2 ||
|
||
p2.lastName.trim().length < 2) {
|
||
return 'Les informations du co-parent sont incomplètes (email, prénom et nom requis).';
|
||
}
|
||
if (p2.email.trim().toLowerCase() == p1.email.trim().toLowerCase()) {
|
||
return 'L’email du co-parent doit être différent de celui du parent principal.';
|
||
}
|
||
final tel2 = _normalizePhone(p2.phone);
|
||
if (tel2.isNotEmpty && !_phoneFr.hasMatch(tel2)) {
|
||
return 'Le numéro de téléphone du co-parent n’est pas valide.';
|
||
}
|
||
}
|
||
}
|
||
|
||
for (var i = 0; i < d.children.length; i++) {
|
||
final c = d.children[i];
|
||
final label = 'Enfant ${i + 1}';
|
||
if (c.isUnbornChild) {
|
||
final due = _ddMmYyyyToIso(c.dob);
|
||
if (due == null) {
|
||
return '$label : indiquez une date de naissance prévisionnelle.';
|
||
}
|
||
} else {
|
||
if (c.firstName.trim().length < 2) {
|
||
return '$label : le prénom doit contenir au moins 2 caractères.';
|
||
}
|
||
final birth = _ddMmYyyyToIso(c.dob);
|
||
if (birth == null) {
|
||
return '$label : indiquez une date de naissance valide.';
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
static Map<String, dynamic> toJson(UserRegistrationData d) {
|
||
final p1 = d.parent1;
|
||
final tel = _normalizePhone(p1.phone);
|
||
|
||
final body = <String, dynamic>{
|
||
'email': p1.email.trim(),
|
||
'prenom': p1.firstName.trim(),
|
||
'nom': p1.lastName.trim(),
|
||
'telephone': tel,
|
||
'acceptation_cgu': d.cguAccepted,
|
||
'acceptation_privacy': d.cguAccepted,
|
||
};
|
||
|
||
_putIfNonEmpty(body, 'adresse', p1.address.trim());
|
||
_putIfNonEmpty(body, 'code_postal', p1.postalCode.trim());
|
||
_putIfNonEmpty(body, 'ville', p1.city.trim());
|
||
|
||
final p2 = d.parent2;
|
||
if (p2 != null &&
|
||
p2.email.trim().isNotEmpty &&
|
||
p2.firstName.trim().length >= 2 &&
|
||
p2.lastName.trim().length >= 2) {
|
||
final tel2 = _normalizePhone(p2.phone);
|
||
final sameAddr = p2.address.trim() == p1.address.trim() &&
|
||
p2.postalCode.trim() == p1.postalCode.trim() &&
|
||
p2.city.trim() == p1.city.trim();
|
||
|
||
body['co_parent_email'] = p2.email.trim();
|
||
body['co_parent_prenom'] = p2.firstName.trim();
|
||
body['co_parent_nom'] = p2.lastName.trim();
|
||
body['co_parent_meme_adresse'] = sameAddr;
|
||
if (tel2.isNotEmpty) {
|
||
body['co_parent_telephone'] = tel2;
|
||
}
|
||
if (!sameAddr) {
|
||
_putIfNonEmpty(body, 'co_parent_adresse', p2.address.trim());
|
||
_putIfNonEmpty(body, 'co_parent_code_postal', p2.postalCode.trim());
|
||
_putIfNonEmpty(body, 'co_parent_ville', p2.city.trim());
|
||
}
|
||
}
|
||
|
||
if (d.motivationText.trim().isNotEmpty) {
|
||
body['presentation_dossier'] = d.motivationText.trim();
|
||
}
|
||
|
||
body['enfants'] = d.children.asMap().entries.map((e) {
|
||
return _childToJson(e.value, e.key, d.parent1.lastName.trim());
|
||
}).toList();
|
||
|
||
return body;
|
||
}
|
||
|
||
static Map<String, dynamic> _childToJson(ChildData c, int index, String parentNom) {
|
||
final map = <String, dynamic>{
|
||
'genre': 'F',
|
||
'grossesse_multiple': c.multipleBirth,
|
||
};
|
||
|
||
final prenom = c.firstName.trim();
|
||
if (prenom.length >= 2) {
|
||
map['prenom'] = prenom;
|
||
}
|
||
|
||
final nom = c.lastName.trim();
|
||
if (nom.length >= 2) {
|
||
map['nom'] = nom;
|
||
} else if (parentNom.length >= 2) {
|
||
map['nom'] = parentNom;
|
||
}
|
||
|
||
if (c.isUnbornChild) {
|
||
final due = _ddMmYyyyToIso(c.dob);
|
||
if (due != null) {
|
||
map['date_previsionnelle_naissance'] = due;
|
||
}
|
||
} else {
|
||
final birth = _ddMmYyyyToIso(c.dob);
|
||
if (birth != null) {
|
||
map['date_naissance'] = birth;
|
||
}
|
||
}
|
||
|
||
final photo = _childPhotoBase64(c, index, prenom);
|
||
if (photo != null) {
|
||
map['photo_base64'] = photo.$1;
|
||
map['photo_filename'] = photo.$2;
|
||
}
|
||
|
||
return map;
|
||
}
|
||
|
||
/// (`dataUrl`, `filename`) ou `null` si pas de fichier lisible.
|
||
static (String, String)? _childPhotoBase64(ChildData c, int index, String prenom) {
|
||
final file = c.imageFile;
|
||
if (file == null) return null;
|
||
try {
|
||
if (!file.existsSync()) return null;
|
||
final bytes = file.readAsBytesSync();
|
||
if (bytes.isEmpty) return null;
|
||
final b64 = base64Encode(bytes);
|
||
final safeName = prenom.isNotEmpty
|
||
? '${prenom.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '_')}.jpg'
|
||
: 'enfant_${index + 1}.jpg';
|
||
return ('data:image/jpeg;base64,$b64', safeName);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
static void _putIfNonEmpty(Map<String, dynamic> m, String key, String value) {
|
||
if (value.isNotEmpty) {
|
||
m[key] = value;
|
||
}
|
||
}
|
||
|
||
static String _normalizePhone(String raw) {
|
||
var s = raw.replaceAll(RegExp(r'\s'), '');
|
||
if (s.startsWith('0033')) {
|
||
s = '+33${s.substring(4)}';
|
||
}
|
||
return s;
|
||
}
|
||
|
||
/// `jj/mm/aaaa` → `aaaa-mm-jj`, ou `null` si invalide.
|
||
static String? _ddMmYyyyToIso(String ddMmYyyy) {
|
||
if (ddMmYyyy.isEmpty) return null;
|
||
final parts = ddMmYyyy.split('/');
|
||
if (parts.length != 3) return null;
|
||
final day = int.tryParse(parts[0]);
|
||
final month = int.tryParse(parts[1]);
|
||
final year = int.tryParse(parts[2]);
|
||
if (day == null || month == null || year == null) return null;
|
||
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
||
return '${year.toString().padLeft(4, '0')}-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||
}
|
||
}
|