Refactor code for improved readability and consistency in multiple files

This commit is contained in:
Leon Bösche
2026-01-23 23:21:46 +01:00
parent 20bc0ac757
commit 98e7bbdb9e
8 changed files with 206 additions and 145 deletions

View File

@@ -18,7 +18,9 @@ class PermissionBloc extends Bloc<PermissionEvent, PermissionState> {
) async { ) async {
emit(PermissionLoading()); emit(PermissionLoading());
try { try {
final response = await apiClient.getRaw('/orgs/${event.orgId}/permissions'); final response = await apiClient.getRaw(
'/orgs/${event.orgId}/permissions',
);
final capabilities = Capabilities( final capabilities = Capabilities(
canRead: response['canRead'] ?? false, canRead: response['canRead'] ?? false,
canWrite: response['canWrite'] ?? false, canWrite: response['canWrite'] ?? false,

View File

@@ -56,7 +56,9 @@ class Invitation {
role: json['role'], role: json['role'],
createdAt: DateTime.parse(json['createdAt']), createdAt: DateTime.parse(json['createdAt']),
expiresAt: DateTime.parse(json['expiresAt']), expiresAt: DateTime.parse(json['expiresAt']),
acceptedAt: json['acceptedAt'] != null ? DateTime.parse(json['acceptedAt']) : null, acceptedAt: json['acceptedAt'] != null
? DateTime.parse(json['acceptedAt'])
: null,
); );
} }
} }

View File

@@ -370,17 +370,24 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
); );
} }
Widget _buildNavButton(String label, IconData icon, {bool isAvatar = false, VoidCallback? onTap}) { Widget _buildNavButton(
String label,
IconData icon, {
bool isAvatar = false,
VoidCallback? onTap,
}) {
final isSelected = _selectedTab == label; final isSelected = _selectedTab == label;
final highlightColor = const Color.fromARGB(255, 100, 200, 255); final highlightColor = const Color.fromARGB(255, 100, 200, 255);
final defaultColor = AppTheme.secondaryText; final defaultColor = AppTheme.secondaryText;
return GestureDetector( return GestureDetector(
onTap: onTap ?? () { onTap:
setState(() { onTap ??
_selectedTab = label; () {
}); setState(() {
}, _selectedTab = label;
});
},
child: isAvatar child: isAvatar
? CircleAvatar( ? CircleAvatar(
backgroundColor: isSelected ? highlightColor : defaultColor, backgroundColor: isSelected ? highlightColor : defaultColor,
@@ -495,7 +502,12 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
const SizedBox(width: 16), const SizedBox(width: 16),
_buildNavButton('Add', Icons.add), _buildNavButton('Add', Icons.add),
const SizedBox(width: 16), const SizedBox(width: 16),
_buildNavButton('Settings', Icons.settings, onTap: () => _showOrganizationSettings(context)), _buildNavButton(
'Settings',
Icons.settings,
onTap: () =>
_showOrganizationSettings(context),
),
const SizedBox(width: 16), const SizedBox(width: 16),
_buildNavButton( _buildNavButton(
'Profile', 'Profile',

View File

@@ -71,7 +71,10 @@ class ApiClient {
} }
} }
Future<Map<String, dynamic>> getRaw(String path, {Map<String, dynamic>? queryParameters}) async { Future<Map<String, dynamic>> getRaw(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
try { try {
final response = await _dio.get(path, queryParameters: queryParameters); final response = await _dio.get(path, queryParameters: queryParameters);
return response.data; return response.data;

View File

@@ -39,7 +39,11 @@ class OrgApi {
); );
} }
Future<void> updateMemberRole(String orgId, String userId, String role) async { Future<void> updateMemberRole(
String orgId,
String userId,
String role,
) async {
await _apiClient.patch( await _apiClient.patch(
'/orgs/$orgId/members/$userId', '/orgs/$orgId/members/$userId',
data: {'role': role}, data: {'role': role},
@@ -58,7 +62,11 @@ class OrgApi {
); );
} }
Future<Invitation> createInvitation(String orgId, String username, String role) async { Future<Invitation> createInvitation(
String orgId,
String username,
String role,
) async {
final result = await _apiClient.post( final result = await _apiClient.post(
'/orgs/$orgId/invitations', '/orgs/$orgId/invitations',
data: {'username': username, 'role': role}, data: {'username': username, 'role': role},
@@ -78,7 +86,10 @@ class OrgApi {
await _apiClient.delete('/orgs/$orgId/invitations/$invitationId'); await _apiClient.delete('/orgs/$orgId/invitations/$invitationId');
} }
Future<JoinRequest> createJoinRequest(String orgId, {String? inviteToken}) async { Future<JoinRequest> createJoinRequest(
String orgId, {
String? inviteToken,
}) async {
final data = {'orgId': orgId}; final data = {'orgId': orgId};
if (inviteToken != null) { if (inviteToken != null) {
data['inviteToken'] = inviteToken; data['inviteToken'] = inviteToken;
@@ -98,7 +109,11 @@ class OrgApi {
); );
} }
Future<void> acceptJoinRequest(String orgId, String requestId, String role) async { Future<void> acceptJoinRequest(
String orgId,
String requestId,
String role,
) async {
await _apiClient.post( await _apiClient.post(
'/orgs/$orgId/join-requests/$requestId/accept', '/orgs/$orgId/join-requests/$requestId/accept',
data: {'role': role}, data: {'role': role},

View File

@@ -20,10 +20,12 @@ class OrganizationSettingsDialog extends StatefulWidget {
}); });
@override @override
State<OrganizationSettingsDialog> createState() => _OrganizationSettingsDialogState(); State<OrganizationSettingsDialog> createState() =>
_OrganizationSettingsDialogState();
} }
class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog> with TickerProviderStateMixin { class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
with TickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
List<Member> _members = []; List<Member> _members = [];
List<Invitation> _invitations = []; List<Invitation> _invitations = [];
@@ -77,46 +79,54 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
Future<void> _updateMemberRole(String userId, String newRole) async { Future<void> _updateMemberRole(String userId, String newRole) async {
try { try {
await widget.orgApi.updateMemberRole(widget.organization.id, userId, newRole); await widget.orgApi.updateMemberRole(
widget.organization.id,
userId,
newRole,
);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to update role: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to update role: $e')));
} }
} }
Future<void> _removeMember(String userId) async { Future<void> _removeMember(String userId) async {
try { try {
await widget.orgApi.removeMember(widget.organization.id, userId); await widget.orgApi.removeMember(widget.organization.id, userId);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to remove member: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to remove member: $e')));
} }
} }
Future<void> _inviteUser(String username, String role) async { Future<void> _inviteUser(String username, String role) async {
try { try {
await widget.orgApi.createInvitation(
await widget.orgApi.createInvitation(widget.organization.id, username, role); widget.organization.id,
username,
role,
);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to send invitation: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to send invitation: $e')));
} }
} }
Future<void> _cancelInvitation(String invitationId) async { Future<void> _cancelInvitation(String invitationId) async {
try { try {
await widget.orgApi.cancelInvitation(
await widget.orgApi.cancelInvitation(widget.organization.id, invitationId); widget.organization.id,
invitationId,
);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
@@ -128,40 +138,43 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
Future<void> _acceptJoinRequest(String requestId, String role) async { Future<void> _acceptJoinRequest(String requestId, String role) async {
try { try {
await widget.orgApi.acceptJoinRequest(
await widget.orgApi.acceptJoinRequest(widget.organization.id, requestId, role); widget.organization.id,
requestId,
role,
);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to accept request: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to accept request: $e')));
} }
} }
Future<void> _rejectJoinRequest(String requestId) async { Future<void> _rejectJoinRequest(String requestId) async {
try { try {
await widget.orgApi.rejectJoinRequest(widget.organization.id, requestId); await widget.orgApi.rejectJoinRequest(widget.organization.id, requestId);
await _loadData(); // Refresh await _loadData(); // Refresh
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to reject request: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to reject request: $e')));
} }
} }
Future<void> _regenerateInviteLink() async { Future<void> _regenerateInviteLink() async {
try { try {
final newLink = await widget.orgApi.regenerateInviteLink(
final newLink = await widget.orgApi.regenerateInviteLink(widget.organization.id); widget.organization.id,
);
setState(() => _inviteLink = newLink); setState(() => _inviteLink = newLink);
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Failed to regenerate link: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Failed to regenerate link: $e')));
} }
} }
@@ -174,7 +187,8 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
} }
} }
bool get _canManage => widget.permissionState is PermissionLoaded && bool get _canManage =>
widget.permissionState is PermissionLoaded &&
(widget.permissionState as PermissionLoaded).capabilities.canAdmin; (widget.permissionState as PermissionLoaded).capabilities.canAdmin;
@override @override
@@ -228,28 +242,31 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: _error != null : _error != null
? Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text(_error!, style: TextStyle(color: AppTheme.errorColor)), Text(
const SizedBox(height: 16), _error!,
ModernGlassButton( style: TextStyle(color: AppTheme.errorColor),
onPressed: _loadData,
child: const Text('Retry'),
),
],
), ),
) const SizedBox(height: 16),
: TabBarView( ModernGlassButton(
controller: _tabController, onPressed: _loadData,
children: [ child: const Text('Retry'),
_buildMembersTab(), ),
_buildInviteTab(), ],
_buildRequestsTab(), ),
_buildLinkTab(), )
], : TabBarView(
), controller: _tabController,
children: [
_buildMembersTab(),
_buildInviteTab(),
_buildRequestsTab(),
_buildLinkTab(),
],
),
), ),
], ],
), ),
@@ -271,31 +288,36 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
member.role, member.role,
style: TextStyle(color: AppTheme.secondaryText), style: TextStyle(color: AppTheme.secondaryText),
), ),
trailing: _canManage ? Row( trailing: _canManage
mainAxisSize: MainAxisSize.min, ? Row(
children: [ mainAxisSize: MainAxisSize.min,
if (member.role != 'owner') children: [
DropdownButton<String>( if (member.role != 'owner')
value: member.role, DropdownButton<String>(
items: ['admin', 'member'].map((role) { value: member.role,
return DropdownMenuItem( items: ['admin', 'member'].map((role) {
value: role, return DropdownMenuItem(
child: Text(role), value: role,
); child: Text(role),
}).toList(), );
onChanged: (newRole) { }).toList(),
if (newRole != null && newRole != member.role) { onChanged: (newRole) {
_updateMemberRole(member.userId, newRole); if (newRole != null && newRole != member.role) {
} _updateMemberRole(member.userId, newRole);
}, }
), },
if (member.role != 'owner') ),
IconButton( if (member.role != 'owner')
icon: Icon(Icons.remove_circle, color: AppTheme.errorColor), IconButton(
onPressed: () => _removeMember(member.userId), icon: Icon(
), Icons.remove_circle,
], color: AppTheme.errorColor,
) : null, ),
onPressed: () => _removeMember(member.userId),
),
],
)
: null,
); );
}, },
); );
@@ -323,8 +345,14 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
itemBuilder: (context, index) { itemBuilder: (context, index) {
final inv = _invitations[index]; final inv = _invitations[index];
return ListTile( return ListTile(
title: Text(inv.username, style: TextStyle(color: AppTheme.primaryText)), title: Text(
subtitle: Text('Role: ${inv.role}', style: TextStyle(color: AppTheme.secondaryText)), inv.username,
style: TextStyle(color: AppTheme.primaryText),
),
subtitle: Text(
'Role: ${inv.role}',
style: TextStyle(color: AppTheme.secondaryText),
),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.cancel, color: AppTheme.errorColor), icon: Icon(Icons.cancel, color: AppTheme.errorColor),
onPressed: () => _cancelInvitation(inv.id), onPressed: () => _cancelInvitation(inv.id),
@@ -358,10 +386,7 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
initialValue: selectedRole, initialValue: selectedRole,
items: ['admin', 'member'].map((role) { items: ['admin', 'member'].map((role) {
return DropdownMenuItem( return DropdownMenuItem(value: role, child: Text(role));
value: role,
child: Text(role),
);
}).toList(), }).toList(),
onChanged: (value) => selectedRole = value ?? 'member', onChanged: (value) => selectedRole = value ?? 'member',
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -399,19 +424,24 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
'Requested to join', 'Requested to join',
style: TextStyle(color: AppTheme.secondaryText), style: TextStyle(color: AppTheme.secondaryText),
), ),
trailing: _canManage ? Row( trailing: _canManage
mainAxisSize: MainAxisSize.min, ? Row(
children: [ mainAxisSize: MainAxisSize.min,
TextButton( children: [
onPressed: () => _acceptJoinRequest(req.id, 'member'), TextButton(
child: const Text('Accept'), onPressed: () => _acceptJoinRequest(req.id, 'member'),
), child: const Text('Accept'),
TextButton( ),
onPressed: () => _rejectJoinRequest(req.id), TextButton(
child: Text('Reject', style: TextStyle(color: AppTheme.errorColor)), onPressed: () => _rejectJoinRequest(req.id),
), child: Text(
], 'Reject',
) : null, style: TextStyle(color: AppTheme.errorColor),
),
),
],
)
: null,
); );
}, },
); );
@@ -429,10 +459,7 @@ class _OrganizationSettingsDialogState extends State<OrganizationSettingsDialog>
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(_inviteLink!, style: TextStyle(color: AppTheme.secondaryText)),
_inviteLink!,
style: TextStyle(color: AppTheme.secondaryText),
),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [

View File

@@ -101,12 +101,12 @@ type Session struct {
} }
type Organization struct { type Organization struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
OwnerID uuid.UUID `json:"ownerId"` OwnerID uuid.UUID `json:"ownerId"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
InviteLinkToken *string `json:"inviteLinkToken,omitempty"` InviteLinkToken *string `json:"inviteLinkToken,omitempty"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
} }
type Membership struct { type Membership struct {
@@ -128,12 +128,12 @@ type Invitation struct {
} }
type JoinRequest struct { type JoinRequest struct {
ID uuid.UUID ID uuid.UUID
OrgID uuid.UUID OrgID uuid.UUID
UserID uuid.UUID UserID uuid.UUID
InviteToken *string InviteToken *string
RequestedAt time.Time RequestedAt time.Time
Status string Status string
} }
type Activity struct { type Activity struct {

View File

@@ -930,10 +930,10 @@ func removeMemberHandler(w http.ResponseWriter, r *http.Request, db *database.DB
resource := userID.String() resource := userID.String()
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
OrgID: &orgID, OrgID: &orgID,
Action: "remove_member", Action: "remove_member",
Resource: &resource, Resource: &resource,
Success: true, Success: true,
}) })
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@@ -993,11 +993,11 @@ func createInvitationHandler(w http.ResponseWriter, r *http.Request, db *databas
} }
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
UserID: &invitedBy, UserID: &invitedBy,
OrgID: &orgID, OrgID: &orgID,
Action: "create_invitation", Action: "create_invitation",
Resource: &req.Username, Resource: &req.Username,
Success: true, Success: true,
}) })
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@@ -1035,10 +1035,10 @@ func cancelInvitationHandler(w http.ResponseWriter, r *http.Request, db *databas
resource := invitationID.String() resource := invitationID.String()
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
OrgID: &orgID, OrgID: &orgID,
Action: "cancel_invitation", Action: "cancel_invitation",
Resource: &resource, Resource: &resource,
Success: true, Success: true,
}) })
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@@ -1142,10 +1142,10 @@ func acceptJoinRequestHandler(w http.ResponseWriter, r *http.Request, db *databa
resource := requestID.String() resource := requestID.String()
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
OrgID: &orgID, OrgID: &orgID,
Action: "accept_join_request", Action: "accept_join_request",
Resource: &resource, Resource: &resource,
Success: true, Success: true,
}) })
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@@ -1169,10 +1169,10 @@ func rejectJoinRequestHandler(w http.ResponseWriter, r *http.Request, db *databa
resource := requestID.String() resource := requestID.String()
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
OrgID: &orgID, OrgID: &orgID,
Action: "reject_join_request", Action: "reject_join_request",
Resource: &resource, Resource: &resource,
Success: true, Success: true,
}) })
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)