Fix org creation from initial state and wrong password error handling

- Organization creation now works even before orgs are loaded (fixes state guard)
- Org creation UI now preserves state and shows inline error messages
- Wrong password login no longer triggers global logout; shows inline error instead
- ApiClient now excludes /auth/ endpoints from global 401 session expiry
This commit is contained in:
Leon Bösche
2026-01-11 00:28:02 +01:00
parent 6186c4c779
commit 7cf55325d4
2 changed files with 55 additions and 36 deletions

View File

@@ -97,41 +97,56 @@ class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
CreateOrganization event, CreateOrganization event,
Emitter<OrganizationState> emit, Emitter<OrganizationState> emit,
) async { ) async {
final currentState = state;
if (currentState is OrganizationLoaded) {
final name = event.name.trim(); final name = event.name.trim();
if (name.isEmpty) { if (name.isEmpty) {
// Try to preserve current state if possible
if (state is OrganizationLoaded) {
emit( emit(
OrganizationLoaded( OrganizationLoaded(
organizations: currentState.organizations, organizations: (state as OrganizationLoaded).organizations,
selectedOrg: currentState.selectedOrg, selectedOrg: (state as OrganizationLoaded).selectedOrg,
isLoading: false, isLoading: false,
error: 'Organization name cannot be empty', error: 'Organization name cannot be empty',
), ),
); );
}
return; return;
} }
if (currentState.organizations.any((org) => org.name == name)) {
// Get existing organizations list
List<Organization> existingOrgs = [];
Organization? selectedOrg;
if (state is OrganizationLoaded) {
existingOrgs = (state as OrganizationLoaded).organizations;
selectedOrg = (state as OrganizationLoaded).selectedOrg;
// Check for duplicate name (client-side validation)
if (existingOrgs.any((org) => org.name == name)) {
emit( emit(
OrganizationLoaded( OrganizationLoaded(
organizations: currentState.organizations, organizations: existingOrgs,
selectedOrg: currentState.selectedOrg, selectedOrg: selectedOrg,
isLoading: false, isLoading: false,
error: 'Organization with this name already exists', error: 'Organization with this name already exists',
), ),
); );
return; return;
} }
}
// Set loading state
emit( emit(
OrganizationLoaded( OrganizationLoaded(
organizations: currentState.organizations, organizations: existingOrgs,
selectedOrg: currentState.selectedOrg, selectedOrg: selectedOrg,
isLoading: true, isLoading: true,
), ),
); );
try { try {
final newOrg = await orgApi.createOrganization(name); final newOrg = await orgApi.createOrganization(name);
final updatedOrgs = [...currentState.organizations, newOrg]; final updatedOrgs = [...existingOrgs, newOrg];
emit( emit(
OrganizationLoaded(organizations: updatedOrgs, selectedOrg: newOrg), OrganizationLoaded(organizations: updatedOrgs, selectedOrg: newOrg),
); );
@@ -143,13 +158,12 @@ class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
} catch (e) { } catch (e) {
emit( emit(
OrganizationLoaded( OrganizationLoaded(
organizations: currentState.organizations, organizations: existingOrgs,
selectedOrg: currentState.selectedOrg, selectedOrg: selectedOrg,
isLoading: false, isLoading: false,
error: _getErrorMessage(e), error: _getErrorMessage(e),
), ),
); );
} }
} }
}
} }

View File

@@ -29,9 +29,14 @@ class ApiClient {
}, },
onError: (error, handler) async { onError: (error, handler) async {
if (error.response?.statusCode == 401) { if (error.response?.statusCode == 401) {
final path = error.requestOptions.path;
// Do not expire session for auth endpoints; show inline error instead
final isAuthEndpoint = path.startsWith('/auth/');
if (!isAuthEndpoint) {
// Session expired, trigger logout // Session expired, trigger logout
_sessionBloc.add(SessionExpired()); _sessionBloc.add(SessionExpired());
} }
}
return handler.next(error); return handler.next(error);
}, },
), ),