Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8104e7e4 | ||
|
|
c4d93ee458 | ||
|
|
1fca0cf132 | ||
|
|
b16dd4b55c | ||
|
|
8682421453 | ||
|
|
dfe7daed14 | ||
|
|
11aa66feff | ||
|
|
d23f3c9f4f |
@@ -0,0 +1,18 @@
|
|||||||
|
# Fins de ligne : toujours LF dans le dépôt (évite les conflits Linux/Windows)
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Fichiers binaires : pas de conversion
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.webp binary
|
||||||
|
*.pdf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
|
||||||
|
# Scripts shell : toujours LF
|
||||||
|
*.sh text eol=lf
|
||||||
@@ -32,30 +32,42 @@ class AppUser {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory AppUser.fromJson(Map<String, dynamic> json) {
|
factory AppUser.fromJson(Map<String, dynamic> json) {
|
||||||
|
final id = json['id']?.toString();
|
||||||
|
final email = json['email']?.toString();
|
||||||
|
final role = json['role']?.toString();
|
||||||
|
if (id == null || id.isEmpty) {
|
||||||
|
throw Exception('Profil invalide: id manquant');
|
||||||
|
}
|
||||||
|
if (email == null || email.isEmpty) {
|
||||||
|
throw Exception('Profil invalide: email manquant');
|
||||||
|
}
|
||||||
|
if (role == null || role.isEmpty) {
|
||||||
|
throw Exception('Profil invalide: rôle manquant');
|
||||||
|
}
|
||||||
return AppUser(
|
return AppUser(
|
||||||
id: json['id'] as String,
|
id: id,
|
||||||
email: json['email'] as String,
|
email: email,
|
||||||
role: json['role'] as String,
|
role: role,
|
||||||
createdAt: json['cree_le'] != null
|
createdAt: json['cree_le'] != null
|
||||||
? DateTime.parse(json['cree_le'] as String)
|
? DateTime.tryParse(json['cree_le'].toString()) ?? DateTime.now()
|
||||||
: (json['createdAt'] != null
|
: (json['createdAt'] != null
|
||||||
? DateTime.parse(json['createdAt'] as String)
|
? DateTime.tryParse(json['createdAt'].toString()) ?? DateTime.now()
|
||||||
: DateTime.now()),
|
: DateTime.now()),
|
||||||
updatedAt: json['modifie_le'] != null
|
updatedAt: json['modifie_le'] != null
|
||||||
? DateTime.parse(json['modifie_le'] as String)
|
? DateTime.tryParse(json['modifie_le'].toString()) ?? DateTime.now()
|
||||||
: (json['updatedAt'] != null
|
: (json['updatedAt'] != null
|
||||||
? DateTime.parse(json['updatedAt'] as String)
|
? DateTime.tryParse(json['updatedAt'].toString()) ?? DateTime.now()
|
||||||
: DateTime.now()),
|
: DateTime.now()),
|
||||||
changementMdpObligatoire:
|
changementMdpObligatoire:
|
||||||
json['changement_mdp_obligatoire'] as bool? ?? false,
|
json['changement_mdp_obligatoire'] == true,
|
||||||
nom: json['nom'] as String?,
|
nom: json['nom']?.toString(),
|
||||||
prenom: json['prenom'] as String?,
|
prenom: json['prenom']?.toString(),
|
||||||
statut: json['statut'] as String?,
|
statut: json['statut']?.toString(),
|
||||||
telephone: json['telephone'] as String?,
|
telephone: json['telephone']?.toString(),
|
||||||
photoUrl: json['photo_url'] as String?,
|
photoUrl: json['photo_url']?.toString(),
|
||||||
adresse: json['adresse'] as String?,
|
adresse: json['adresse']?.toString(),
|
||||||
ville: json['ville'] as String?,
|
ville: json['ville']?.toString(),
|
||||||
codePostal: json['code_postal'] as String?,
|
codePostal: json['code_postal']?.toString(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,20 +236,20 @@ class _LoginPageState extends State<LoginScreen> with WidgetsBindingObserver {
|
|||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.red[50],
|
color: Colors.red.shade50,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(color: Colors.red[300]!),
|
border: Border.all(color: Colors.red.shade300),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.error_outline, color: Colors.red[700], size: 20),
|
Icon(Icons.error_outline, color: Colors.red.shade700, size: 20),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_errorMessage!,
|
_errorMessage!,
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.red[700],
|
color: Colors.red.shade700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is Exception) rethrow;
|
if (e is Exception) rethrow;
|
||||||
throw Exception('Erreur réseau: impossible de se connecter au serveur');
|
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
|
||||||
|
throw Exception('Erreur réseau: impossible de se connecter au serveur ($e)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +57,22 @@ class AuthService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(response.body);
|
final raw = jsonDecode(response.body);
|
||||||
|
if (raw is! Map<String, dynamic>) {
|
||||||
|
throw Exception('Profil invalide: réponse serveur inattendue');
|
||||||
|
}
|
||||||
|
// Accepter réponse directe ou wrapper { data: {...} }
|
||||||
|
final data = raw.containsKey('data') && raw['data'] is Map<String, dynamic>
|
||||||
|
? raw['data'] as Map<String, dynamic>
|
||||||
|
: raw;
|
||||||
return AppUser.fromJson(data);
|
return AppUser.fromJson(data);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Erreur lors de la récupération du profil');
|
throw Exception('Erreur lors de la récupération du profil');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is Exception) rethrow;
|
if (e is Exception) rethrow;
|
||||||
throw Exception('Erreur réseau: impossible de récupérer le profil');
|
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
|
||||||
|
throw Exception('Erreur réseau: impossible de récupérer le profil ($e)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +107,8 @@ class AuthService {
|
|||||||
await _saveCurrentUser(user);
|
await _saveCurrentUser(user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is Exception) rethrow;
|
if (e is Exception) rethrow;
|
||||||
throw Exception('Erreur réseau: impossible de changer le mot de passe');
|
if (e is Error) throw Exception('Erreur interne: ${e.toString()}');
|
||||||
|
throw Exception('Erreur réseau: impossible de changer le mot de passe ($e)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Créer l’issue #84 (correctifs modale MDP) via l’API Gitea
|
||||||
|
|
||||||
|
1. Définir un token valide :
|
||||||
|
`export GITEA_TOKEN="votre_token"`
|
||||||
|
ou créer `.gitea-token` à la racine du projet avec le token seul.
|
||||||
|
|
||||||
|
2. Créer l’issue :
|
||||||
|
```bash
|
||||||
|
cd /chemin/vers/PetitsPas
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @scripts/issue-84-payload.json \
|
||||||
|
"https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/issues"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. En cas de succès (HTTP 201), la réponse JSON contient le numéro de l’issue créée.
|
||||||
|
|
||||||
|
Payload utilisé : `scripts/issue-84-payload.json` (titre + corps depuis `scripts/issue-84-body.txt`).
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Crée une issue Gitea via l'API.
|
||||||
|
# Usage: GITEA_TOKEN=xxx ./scripts/create-gitea-issue.sh
|
||||||
|
# Ou: mettre le token dans .gitea-token à la racine du projet.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
BASE_URL="${GITEA_URL:-https://git.ptits-pas.fr/api/v1}"
|
||||||
|
REPO="jmartin/petitspas"
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
if [ -f .gitea-token ]; then
|
||||||
|
GITEA_TOKEN=$(cat .gitea-token)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TITLE="$1"
|
||||||
|
BODY="$2"
|
||||||
|
if [ -z "$TITLE" ]; then
|
||||||
|
echo "Usage: $0 \"Titre de l'issue\" \"Corps (optionnel)\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build JSON (escape body for JSON)
|
||||||
|
BODY_ESC=$(echo "$BODY" | jq -Rs . 2>/dev/null || echo "null")
|
||||||
|
if [ "$BODY_ESC" = "null" ] || [ -z "$BODY" ]; then
|
||||||
|
PAYLOAD=$(jq -n --arg t "$TITLE" '{title: $t}')
|
||||||
|
else
|
||||||
|
PAYLOAD=$(jq -n --arg t "$TITLE" --arg b "$BODY" '{title: $t, body: $b}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESP=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
"$BASE_URL/repos/$REPO/issues")
|
||||||
|
HTTP_CODE=$(echo "$RESP" | tail -1)
|
||||||
|
BODY_RESP=$(echo "$RESP" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
ISSUE_NUM=$(echo "$BODY_RESP" | jq -r .number)
|
||||||
|
echo "Issue #$ISSUE_NUM créée."
|
||||||
|
echo "$BODY_RESP" | jq .
|
||||||
|
else
|
||||||
|
echo "Erreur HTTP $HTTP_CODE: $BODY_RESP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Poste un commentaire sur une issue Gitea puis la ferme.
|
||||||
|
# Usage: GITEA_TOKEN=xxx ./scripts/gitea-close-issue-with-comment.sh <numéro> "Commentaire"
|
||||||
|
# Ou: mettre le token dans .gitea-token à la racine du projet.
|
||||||
|
# Exemple: ./scripts/gitea-close-issue-with-comment.sh 15 "Livré : panneau Paramètres opérationnel."
|
||||||
|
|
||||||
|
set -e
|
||||||
|
ISSUE="${1:?Usage: $0 <numéro_issue> \"Commentaire\"}"
|
||||||
|
COMMENT="${2:?Usage: $0 <numéro_issue> \"Commentaire\"}"
|
||||||
|
BASE_URL="${GITEA_URL:-https://git.ptits-pas.fr/api/v1}"
|
||||||
|
REPO="jmartin/petitspas"
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
if [ -f .gitea-token ]; then
|
||||||
|
GITEA_TOKEN=$(cat .gitea-token)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "Définir GITEA_TOKEN ou créer .gitea-token avec votre token Gitea."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1) Poster le commentaire
|
||||||
|
echo "Ajout du commentaire sur l'issue #$ISSUE..."
|
||||||
|
# Échapper pour JSON (guillemets et backslash)
|
||||||
|
COMMENT_ESC=$(printf '%s' "$COMMENT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\r//g')
|
||||||
|
PAYLOAD="{\"body\":\"$COMMENT_ESC\"}"
|
||||||
|
RESP=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
"$BASE_URL/repos/$REPO/issues/$ISSUE/comments")
|
||||||
|
HTTP_CODE=$(echo "$RESP" | tail -1)
|
||||||
|
BODY=$(echo "$RESP" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" != "201" ]; then
|
||||||
|
echo "Erreur HTTP $HTTP_CODE lors du commentaire: $BODY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Commentaire ajouté."
|
||||||
|
|
||||||
|
# 2) Fermer l'issue
|
||||||
|
echo "Fermeture de l'issue #$ISSUE..."
|
||||||
|
RESP2=$(curl -s -w "\n%{http_code}" -X PATCH \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"state":"closed"}' \
|
||||||
|
"$BASE_URL/repos/$REPO/issues/$ISSUE")
|
||||||
|
HTTP_CODE2=$(echo "$RESP2" | tail -1)
|
||||||
|
BODY2=$(echo "$RESP2" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE2" = "200" ] || [ "$HTTP_CODE2" = "201" ]; then
|
||||||
|
echo "Issue #$ISSUE fermée."
|
||||||
|
else
|
||||||
|
echo "Erreur HTTP $HTTP_CODE2: $BODY2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Correctifs et améliorations de la modale de changement de mot de passe obligatoire affichée à la première connexion admin.
|
||||||
|
|
||||||
|
**Périmètre :**
|
||||||
|
- Ajustements visuels / UX de la modale (ChangePasswordDialog)
|
||||||
|
- Cohérence charte graphique, espacements, lisibilité
|
||||||
|
- Comportement (validation, messages d'erreur, fermeture)
|
||||||
|
- Lien de test en debug sur l'écran login (« Test modale MDP ») pour faciliter les réglages
|
||||||
|
|
||||||
|
**Tâches :**
|
||||||
|
- [ ] Revoir le design de la modale (relief, bordures, couleurs)
|
||||||
|
- [ ] Vérifier les champs (MDP actuel, nouveau, confirmation) et validations
|
||||||
|
- [ ] Ajuster les textes et messages d'erreur
|
||||||
|
- [ ] Tester sur mobile et desktop
|
||||||
|
- [ ] Retirer ou conditionner le lien « Test modale MDP » en production si besoin
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"title": "[Frontend] Bug – Correctifs modale Changement MDP (première connexion admin)", "body": "Correctifs et améliorations de la modale de changement de mot de passe obligatoire affichée à la première connexion admin.\n\n**Périmètre :**\n- Ajustements visuels / UX de la modale (ChangePasswordDialog)\n- Cohérence charte graphique, espacements, lisibilité\n- Comportement (validation, messages d'erreur, fermeture)\n- Lien de test en debug sur l'écran login (« Test modale MDP ») pour faciliter les réglages\n\n**Tâches :**\n- [ ] Revoir le design de la modale (relief, bordures, couleurs)\n- [ ] Vérifier les champs (MDP actuel, nouveau, confirmation) et validations\n- [ ] Ajuster les textes et messages d'erreur\n- [ ] Tester sur mobile et desktop\n- [ ] Retirer ou conditionner le lien « Test modale MDP » en production si besoin\n"}
|
||||||
Reference in New Issue
Block a user