From 65ad05ac760def27b7bf6681b053e2feb13eaa82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sat, 24 Jan 2026 04:55:02 +0100 Subject: [PATCH] Refactor invitation and invite link sections for improved readability and layout consistency --- .../widgets/organization_settings_dialog.dart | 413 +++++++++--------- 1 file changed, 208 insertions(+), 205 deletions(-) diff --git a/b0esche_cloud/lib/widgets/organization_settings_dialog.dart b/b0esche_cloud/lib/widgets/organization_settings_dialog.dart index 3036f28..d326920 100644 --- a/b0esche_cloud/lib/widgets/organization_settings_dialog.dart +++ b/b0esche_cloud/lib/widgets/organization_settings_dialog.dart @@ -420,224 +420,227 @@ class _OrganizationSettingsDialogState final List children = [ Column( - children: [ - // Pending invitations - if (_invitations.isNotEmpty) ...[ - Text( - 'Pending Invitations', - style: TextStyle( - color: AppTheme.primaryText, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - SizedBox( - height: 150, - child: ListView.builder( - itemCount: _invitations.length, - itemBuilder: (context, index) { - final inv = _invitations[index]; - return ListTile( - title: Text( - inv.username, - style: TextStyle(color: AppTheme.primaryText), + children: + [ + // Pending invitations + if (_invitations.isNotEmpty) ...[ + Text( + 'Pending Invitations', + style: TextStyle( + color: AppTheme.primaryText, + fontWeight: FontWeight.bold, ), - subtitle: Text( - 'Role: ${inv.role}', - style: TextStyle(color: AppTheme.secondaryText), - ), - trailing: IconButton( - icon: Icon(Icons.cancel, color: AppTheme.errorColor), - onPressed: () => _cancelInvitation(inv.id), - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - ), - ); - }, - ), - ), - ], - - // Invite link section - if (_inviteLink != null) ...[ - const Divider(), - Text( - 'Invite Link', - style: TextStyle( - color: AppTheme.primaryText, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - child: Text( - 'https://b0esche.cloud/join?token=${_inviteLink ?? ''}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: AppTheme.secondaryText), ), - ), - const SizedBox(width: 16), - ModernGlassButton( - onPressed: _copyInviteLink, - child: const Icon(Icons.content_copy), - ), - if (_canManage) ...[ - const SizedBox(width: 16), - ModernGlassButton( - onPressed: _regenerateInviteLink, - child: const Icon(Icons.refresh), - ), - ], - ], - ), - ] else if (_canManage) ...[ - const Divider(), - const Text('No invite link available'), - ], - - // Invite form - const Divider(), - Text( - 'Invite New User', - style: TextStyle( - color: AppTheme.primaryText, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - TextField( - key: textFieldKey, - controller: usernameController, - cursorColor: AppTheme.accentColor, - decoration: InputDecoration( - hintText: 'Username', - hintStyle: TextStyle(color: AppTheme.secondaryText), - contentPadding: const EdgeInsets.all(12), - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppTheme.accentColor.withValues(alpha: 0.3), - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: AppTheme.accentColor.withValues(alpha: 0.3), - ), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.accentColor), - ), - ), - style: TextStyle(color: AppTheme.primaryText), - onChanged: (value) async { - if (value.length > 2) { - try { - _userSuggestions = await widget.orgApi.searchUsers( - widget.organization.id, - value, - ); - } catch (e) { - _userSuggestions = []; - } - setState(() {}); - } else { - _userSuggestions = []; - setState(() {}); - } - }, - ), - DropdownButtonFormField( - initialValue: selectedRole, - items: ['admin', 'member'].map((role) { - return DropdownMenuItem(value: role, child: Text(role)); - }).toList(), - onChanged: (value) => selectedRole = value ?? 'member', - decoration: InputDecoration( - labelText: 'Role', - labelStyle: TextStyle(color: AppTheme.secondaryText), - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppTheme.accentColor.withValues(alpha: 0.3), - ), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.accentColor), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: AppTheme.accentColor.withValues(alpha: 0.3), - ), - ), - ), - ), - const SizedBox(height: 16), - SizedBox( - width: 240, - child: ModernGlassButton( - onPressed: () { - if (_canManage) { - final username = usernameController.text.trim(); - if (username.isNotEmpty) { - _inviteUser(username, selectedRole); - usernameController.clear(); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'You do not have permission to send invitations', - ), - ), - ); - } - }, - child: const Text('Send Invitation'), - ), - ), - ] + (_userSuggestions.isNotEmpty - ? [ - Positioned( - top: 240, - left: 0, - right: 0, - child: Container( - height: 100, - decoration: BoxDecoration( - color: AppTheme.primaryBackground, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: AppTheme.accentColor.withValues(alpha: 0.3), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), + const SizedBox(height: 8), + SizedBox( + height: 150, child: ListView.builder( - itemCount: _userSuggestions.length, + itemCount: _invitations.length, itemBuilder: (context, index) { - final user = _userSuggestions[index]; + final inv = _invitations[index]; return ListTile( title: Text( - user.displayName ?? user.username, + inv.username, style: TextStyle(color: AppTheme.primaryText), ), - onTap: () { - usernameController.text = user.username; - setState(() => _userSuggestions = []); - }, + subtitle: Text( + 'Role: ${inv.role}', + style: TextStyle(color: AppTheme.secondaryText), + ), + trailing: IconButton( + icon: Icon(Icons.cancel, color: AppTheme.errorColor), + onPressed: () => _cancelInvitation(inv.id), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + ), ); }, ), ), + ], + + // Invite link section + if (_inviteLink != null) ...[ + const Divider(), + Text( + 'Invite Link', + style: TextStyle( + color: AppTheme.primaryText, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Text( + 'https://b0esche.cloud/join?token=${_inviteLink ?? ''}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: AppTheme.secondaryText), + ), + ), + const SizedBox(width: 16), + ModernGlassButton( + onPressed: _copyInviteLink, + child: const Icon(Icons.content_copy), + ), + if (_canManage) ...[ + const SizedBox(width: 16), + ModernGlassButton( + onPressed: _regenerateInviteLink, + child: const Icon(Icons.refresh), + ), + ], + ], + ), + ] else if (_canManage) ...[ + const Divider(), + const Text('No invite link available'), + ], + + // Invite form + const Divider(), + Text( + 'Invite New User', + style: TextStyle( + color: AppTheme.primaryText, + fontWeight: FontWeight.bold, + ), ), - ] - : []), + const SizedBox(height: 16), + TextField( + key: textFieldKey, + controller: usernameController, + cursorColor: AppTheme.accentColor, + decoration: InputDecoration( + hintText: 'Username', + hintStyle: TextStyle(color: AppTheme.secondaryText), + contentPadding: const EdgeInsets.all(12), + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppTheme.accentColor.withValues(alpha: 0.3), + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: AppTheme.accentColor.withValues(alpha: 0.3), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: AppTheme.accentColor), + ), + ), + style: TextStyle(color: AppTheme.primaryText), + onChanged: (value) async { + if (value.length > 2) { + try { + _userSuggestions = await widget.orgApi.searchUsers( + widget.organization.id, + value, + ); + } catch (e) { + _userSuggestions = []; + } + setState(() {}); + } else { + _userSuggestions = []; + setState(() {}); + } + }, + ), + DropdownButtonFormField( + initialValue: selectedRole, + items: ['admin', 'member'].map((role) { + return DropdownMenuItem(value: role, child: Text(role)); + }).toList(), + onChanged: (value) => selectedRole = value ?? 'member', + decoration: InputDecoration( + labelText: 'Role', + labelStyle: TextStyle(color: AppTheme.secondaryText), + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppTheme.accentColor.withValues(alpha: 0.3), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: AppTheme.accentColor), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: AppTheme.accentColor.withValues(alpha: 0.3), + ), + ), + ), + ), + const SizedBox(height: 16), + SizedBox( + width: 240, + child: ModernGlassButton( + onPressed: () { + if (_canManage) { + final username = usernameController.text.trim(); + if (username.isNotEmpty) { + _inviteUser(username, selectedRole); + usernameController.clear(); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'You do not have permission to send invitations', + ), + ), + ); + } + }, + child: const Text('Send Invitation'), + ), + ), + ] + + (_userSuggestions.isNotEmpty + ? [ + Positioned( + top: 240, + left: 0, + right: 0, + child: Container( + height: 100, + decoration: BoxDecoration( + color: AppTheme.primaryBackground, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppTheme.accentColor.withValues(alpha: 0.3), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: ListView.builder( + itemCount: _userSuggestions.length, + itemBuilder: (context, index) { + final user = _userSuggestions[index]; + return ListTile( + title: Text( + user.displayName ?? user.username, + style: TextStyle(color: AppTheme.primaryText), + ), + onTap: () { + usernameController.text = user.username; + setState(() => _userSuggestions = []); + }, + ); + }, + ), + ), + ), + ] + : []), + ), ]; if (_userSuggestions.isNotEmpty) {