fix(sf_shared): distinguish 401 vs 403 error UX

This commit is contained in:
2026-04-21 21:34:32 +02:00
parent f36ad5e4a6
commit 0418f16f87
10 changed files with 27 additions and 4 deletions

View File

@@ -57,6 +57,9 @@
"passwordMatch": "Passwörter stimmen überein",
"passwordChangedSuccess": "Passwort erfolgreich aktualisiert",
"personalDataUpdatedSuccess": "Persönliche Daten aktualisiert",
"deviceUpdatedSuccess": "Gerät aktualisiert",
"deviceDeletedSuccess": "Gerät entfernt",
"errorNotAuthorized": "Sie haben keine Berechtigung für diese Aktion.",
"accept": "Akzeptieren",
"errorMessageUnequalPasswords": "Passwörter stimmen nicht überein. versuchen Sie es erneut",
"errorMessagePasswordTooShort": "Das Passwort muss mindestens 8 Zeichen lang sein",

View File

@@ -57,6 +57,9 @@
"passwordMatch": "Passwords match",
"passwordChangedSuccess": "Password updated successfully",
"personalDataUpdatedSuccess": "Personal data updated successfully",
"deviceUpdatedSuccess": "Device updated successfully",
"deviceDeletedSuccess": "Device removed successfully",
"errorNotAuthorized": "You don't have permission to perform this action.",
"accept": "Accept",
"errorMessageUnequalPasswords": "Passwords don't match. Try again",
"errorMessagePasswordTooShort": "Password must include at least 8 characters",

View File

@@ -57,6 +57,9 @@
"passwordMatch": "Las contraseñas coinciden",
"passwordChangedSuccess": "Contraseña actualizada correctamente",
"personalDataUpdatedSuccess": "Datos actualizados correctamente",
"deviceUpdatedSuccess": "Dispositivo actualizado correctamente",
"deviceDeletedSuccess": "Dispositivo eliminado correctamente",
"errorNotAuthorized": "No tienes permiso para realizar esta acción.",
"accept": "Aceptar",
"errorMessageUnequalPasswords": "Las contraseñas no coinciden. Inténtalo de nuevo",
"errorMessagePasswordTooShort": "La contraseña debe tener al menos 8 caracteres",

View File

@@ -57,6 +57,9 @@
"passwordMatch": "Les mots de passe correspondent",
"passwordChangedSuccess": "Mot de passe mis à jour",
"personalDataUpdatedSuccess": "Données personnelles mises à jour",
"deviceUpdatedSuccess": "Appareil mis à jour",
"deviceDeletedSuccess": "Appareil supprimé",
"errorNotAuthorized": "Vous n'avez pas l'autorisation d'effectuer cette action.",
"accept": "Accepter",
"errorMessageUnequalPasswords": "Les mots de passe ne correspondent pas. essayer à nouveau",
"errorMessagePasswordTooShort": "Le mot de passe doit contenir au moins 8 caractères",

View File

@@ -57,6 +57,9 @@
"passwordMatch": "Le password corrispondono",
"passwordChangedSuccess": "Password aggiornata correttamente",
"personalDataUpdatedSuccess": "Dati personali aggiornati",
"deviceUpdatedSuccess": "Dispositivo aggiornato",
"deviceDeletedSuccess": "Dispositivo rimosso",
"errorNotAuthorized": "Non hai i permessi per eseguire questa azione.",
"accept": "Accettare",
"errorMessageUnequalPasswords": "Le password non corrispondono. riprova",
"errorMessagePasswordTooShort": "La password deve contenere almeno 8 caratteri",

View File

@@ -57,6 +57,9 @@
"passwordMatch": "As palavras-passe coincidem",
"passwordChangedSuccess": "Palavra-passe atualizada com sucesso",
"personalDataUpdatedSuccess": "Dados pessoais atualizados",
"deviceUpdatedSuccess": "Dispositivo atualizado",
"deviceDeletedSuccess": "Dispositivo removido",
"errorNotAuthorized": "Não tens permissão para realizar esta ação.",
"accept": "Aceitar",
"errorMessageUnequalPasswords": "Las contraseñas não é coincidência.",
"errorMessagePasswordTooShort": "A senha deve ter pelo menos 8 caracteres",

View File

@@ -253,6 +253,7 @@ class I18n {
static const String depositSchedule = 'depositSchedule';
static const String depositTitle = 'depositTitle';
static const String depositWhenSend = 'depositWhenSend';
static const String deviceDeletedSuccess = 'deviceDeletedSuccess';
static const String deviceFunctionsTitle = 'deviceFunctionsTitle';
static const String deviceIdLabel = 'deviceIdLabel';
static const String deviceNotConnected = 'deviceNotConnected';
@@ -306,6 +307,7 @@ class I18n {
static const String deviceSetupWatchCodeOrInsert = 'deviceSetupWatchCodeOrInsert';
static const String deviceSetupWeightHint = 'deviceSetupWeightHint';
static const String deviceSetupWeightLabel = 'deviceSetupWeightLabel';
static const String deviceUpdatedSuccess = 'deviceUpdatedSuccess';
static const String didNotReceiveIt = 'didNotReceiveIt';
static const String disableFunctions = 'disableFunctions';
static const String disableFunctionsGps = 'disableFunctionsGps';
@@ -404,6 +406,7 @@ class I18n {
static const String errorMessagePhoneIsInvalid = 'errorMessagePhoneIsInvalid';
static const String errorMessageUnequalPasswords = 'errorMessageUnequalPasswords';
static const String errorNameInvalidChars = 'errorNameInvalidChars';
static const String errorNotAuthorized = 'errorNotAuthorized';
static const String errorNotFound = 'errorNotFound';
static const String errorOfflineMessage = 'errorOfflineMessage';
static const String errorOfflineRetry = 'errorOfflineRetry';

View File

@@ -21,7 +21,7 @@ enum FailureType {
final status = exception.statusCode;
if (status == null) return FailureType.other;
return switch (status) {
401 => FailureType.notAuthorized,
401 => FailureType.invalidCredentials,
403 => FailureType.notAuthorized,
404 => FailureType.notFound,
422 => FailureType.validation,

View File

@@ -28,7 +28,9 @@ Future<void> handleFailure({
await showOfflineDialog(context, onRetry: onRetry);
case FailureType.technical || FailureType.other:
await showTechnicalErrorDialog(context);
case FailureType.notAuthorized || FailureType.invalidCredentials:
case FailureType.notAuthorized:
await showErrorDialog(context, I18n.errorNotAuthorized);
case FailureType.invalidCredentials:
await showErrorDialog(context, I18n.errorInvalidCredentials);
case FailureType.notFound:
await showErrorDialog(context, I18n.errorNotFound);

View File

@@ -10,9 +10,9 @@ void main() {
expect(FailureType.fromException(e), FailureType.connection);
});
test('maps ApiException 401 to notAuthorized', () {
test('maps ApiException 401 to invalidCredentials', () {
final e = const ApiException(message: 'm', statusCode: 401);
expect(FailureType.fromException(e), FailureType.notAuthorized);
expect(FailureType.fromException(e), FailureType.invalidCredentials);
});
test('maps ApiException 403 to notAuthorized', () {