feat(inscription AM #120): UI étape pro, photo unifiée, scripts et données test
- Panneau AM : validation NIR alignée API, date d’agrément requise côté app, capacité 1–10, n° agrément + date sur une ligne, ville/pays formatés au blur. - Widget RegistrationPhotoSlot (cadre, croix) partagé avec les cartes enfant. - AuthService : MIME PNG/JPEG pour la photo ; payload date_agrement. - Scripts register-am-dubois / mansouri ; chemins tests/ressources/photos ; doc test-data + seed ; smoke curl inscription AM. Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:http/http.dart' as http;
|
||||
@@ -22,6 +23,17 @@ class AuthService {
|
||||
return name.isEmpty ? null : name;
|
||||
}
|
||||
|
||||
static String _imageMimeForBytes(Uint8List bytes) {
|
||||
if (bytes.length >= 8 &&
|
||||
bytes[0] == 0x89 &&
|
||||
bytes[1] == 0x50 &&
|
||||
bytes[2] == 0x4E &&
|
||||
bytes[3] == 0x47) {
|
||||
return 'image/png';
|
||||
}
|
||||
return 'image/jpeg';
|
||||
}
|
||||
|
||||
/// Connexion de l'utilisateur
|
||||
/// Retourne l'utilisateur connecté avec le flag changement_mdp_obligatoire
|
||||
static Future<AppUser> login(String email, String password) async {
|
||||
@@ -150,11 +162,15 @@ class AuthService {
|
||||
/// Inscription AM complète (POST /auth/register/am).
|
||||
/// En cas de succès (201), aucune donnée utilisateur retournée ; rediriger vers login.
|
||||
static Future<void> registerAM(AmRegistrationData data) async {
|
||||
if (data.agreementDate == null) {
|
||||
throw Exception('La date d\'obtention de l\'agrément est requise.');
|
||||
}
|
||||
String? photoBase64;
|
||||
String? photoFilename;
|
||||
|
||||
if (data.photoBytes != null && data.photoBytes!.isNotEmpty) {
|
||||
photoBase64 = 'data:image/jpeg;base64,${base64Encode(data.photoBytes!)}';
|
||||
final mime = _imageMimeForBytes(data.photoBytes!);
|
||||
photoBase64 = 'data:$mime;base64,${base64Encode(data.photoBytes!)}';
|
||||
final fn = (data.photoFilename ?? '').trim();
|
||||
photoFilename = fn.isNotEmpty ? fn : 'photo_am.jpg';
|
||||
} else if (data.photoPath != null &&
|
||||
@@ -165,7 +181,8 @@ class AuthService {
|
||||
final file = File(data.photoPath!);
|
||||
if (await file.exists()) {
|
||||
final bytes = await file.readAsBytes();
|
||||
photoBase64 = 'data:image/jpeg;base64,${base64Encode(bytes)}';
|
||||
final mime = _imageMimeForBytes(bytes);
|
||||
photoBase64 = 'data:$mime;base64,${base64Encode(bytes)}';
|
||||
photoFilename =
|
||||
_basenameFromPath(data.photoPath!) ?? 'photo_am.jpg';
|
||||
}
|
||||
@@ -191,6 +208,8 @@ class AuthService {
|
||||
'lieu_naissance_pays': data.birthCountry.isNotEmpty ? data.birthCountry : null,
|
||||
'nir': normalizeNir(data.nir),
|
||||
'numero_agrement': data.agrementNumber,
|
||||
'date_agrement':
|
||||
'${data.agreementDate!.year}-${data.agreementDate!.month.toString().padLeft(2, '0')}-${data.agreementDate!.day.toString().padLeft(2, '0')}',
|
||||
'capacite_accueil': data.capacity ?? 1,
|
||||
'biographie': data.presentationText.isNotEmpty ? data.presentationText : null,
|
||||
'acceptation_cgu': data.cguAccepted,
|
||||
|
||||
Reference in New Issue
Block a user