diff --git a/b0esche_cloud/lib/widgets/account_settings_dialog.dart b/b0esche_cloud/lib/widgets/account_settings_dialog.dart index f1ada9b..8aef798 100644 --- a/b0esche_cloud/lib/widgets/account_settings_dialog.dart +++ b/b0esche_cloud/lib/widgets/account_settings_dialog.dart @@ -28,7 +28,6 @@ class _AccountSettingsDialogState extends State { // Profile fields late TextEditingController _displayNameController; - late TextEditingController _emailController; String? _avatarUrl; String? _blurHash; @@ -43,7 +42,6 @@ class _AccountSettingsDialogState extends State { void initState() { super.initState(); _displayNameController = TextEditingController(); - _emailController = TextEditingController(); _currentPasswordController = TextEditingController(); _newPasswordController = TextEditingController(); _confirmPasswordController = TextEditingController(); @@ -53,7 +51,6 @@ class _AccountSettingsDialogState extends State { @override void dispose() { _displayNameController.dispose(); - _emailController.dispose(); _currentPasswordController.dispose(); _newPasswordController.dispose(); _confirmPasswordController.dispose(); @@ -65,7 +62,6 @@ class _AccountSettingsDialogState extends State { if (authState is AuthAuthenticated) { _currentUser = authState.user; _displayNameController.text = _currentUser?.displayName ?? ''; - _emailController.text = _currentUser?.email ?? ''; _avatarUrl = _currentUser?.avatarUrl; _blurHash = _currentUser?.blurHash; } @@ -135,7 +131,6 @@ class _AccountSettingsDialogState extends State { displayName: _displayNameController.text.isEmpty ? null : _displayNameController.text, - email: _emailController.text, avatarUrl: _avatarUrl, blurHash: _blurHash, ); @@ -144,7 +139,6 @@ class _AccountSettingsDialogState extends State { displayName: _displayNameController.text.isEmpty ? null : _displayNameController.text, - email: _emailController.text, avatarUrl: _avatarUrl, blurHash: _blurHash, ); @@ -340,6 +334,7 @@ class _AccountSettingsDialogState extends State { Center( child: Column( children: [ + SizedBox(height: 16), GestureDetector( onTap: _pickAndUploadAvatar, child: CircleAvatar( @@ -397,41 +392,27 @@ class _AccountSettingsDialogState extends State { ), ), ), - const SizedBox(height: 16), + const SizedBox(height: 24), - // Email - Text( - 'Email', - style: TextStyle( - color: AppTheme.primaryText, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - TextFormField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - style: TextStyle(color: AppTheme.primaryText), - decoration: InputDecoration( - hintText: 'Enter email', - hintStyle: TextStyle(color: AppTheme.secondaryText), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: AppTheme.secondaryText), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: AppTheme.secondaryText), + // Save Button + Center( + child: SizedBox( + width: 88, + child: ModernGlassButton( + onPressed: _updateProfile, + child: const Text('Save Changes'), ), ), ), const SizedBox(height: 24), - // Save Button + // Logout Button Center( - child: ModernGlassButton( - onPressed: _updateProfile, - child: const Text('Save Changes'), + child: IconButton( + onPressed: _logout, + icon: Icon(Icons.logout, color: AppTheme.errorColor), + tooltip: 'Logout', + iconSize: 28, ), ), ], @@ -530,19 +511,12 @@ class _AccountSettingsDialogState extends State { // Change Password Button Center( - child: ModernGlassButton( - onPressed: _changePassword, - child: const Text('Change Password'), - ), - ), - const SizedBox(height: 24), - - // Logout Button - Center( - child: TextButton( - onPressed: _logout, - style: TextButton.styleFrom(foregroundColor: AppTheme.errorColor), - child: const Text('Logout'), + child: SizedBox( + width: 88, + child: ModernGlassButton( + onPressed: _changePassword, + child: const Text('Change Password'), + ), ), ), ], diff --git a/go_cloud/api b/go_cloud/api index 89790e5..73569bd 100755 Binary files a/go_cloud/api and b/go_cloud/api differ diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 26736ec..4b80a09 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -3735,7 +3735,7 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa var req struct { DisplayName *string `json:"displayName"` - Email string `json:"email"` + Email *string `json:"email"` AvatarURL *string `json:"avatarUrl"` BlurHash *string `json:"blurHash"` } @@ -3745,11 +3745,44 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa return } - // Update user - _, err = db.ExecContext(r.Context(), - `UPDATE users SET display_name = $1, email = $2, avatar_url = $3, blur_hash = $4, updated_at = NOW() - WHERE id = $5`, - req.DisplayName, req.Email, req.AvatarURL, req.BlurHash, userID) + // Build dynamic update query + var setParts []string + var args []interface{} + argIndex := 1 + + if req.DisplayName != nil { + setParts = append(setParts, fmt.Sprintf("display_name = $%d", argIndex)) + args = append(args, *req.DisplayName) + argIndex++ + } + if req.Email != nil { + setParts = append(setParts, fmt.Sprintf("email = $%d", argIndex)) + args = append(args, *req.Email) + argIndex++ + } + if req.AvatarURL != nil { + setParts = append(setParts, fmt.Sprintf("avatar_url = $%d", argIndex)) + args = append(args, *req.AvatarURL) + argIndex++ + } + if req.BlurHash != nil { + setParts = append(setParts, fmt.Sprintf("blur_hash = $%d", argIndex)) + args = append(args, *req.BlurHash) + argIndex++ + } + + if len(setParts) == 0 { + // No fields to update + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "No changes to update"}) + return + } + + setParts = append(setParts, "updated_at = NOW()") + query := fmt.Sprintf("UPDATE users SET %s WHERE id = $%d", strings.Join(setParts, ", "), argIndex) + args = append(args, userID) + + _, err = db.ExecContext(r.Context(), query, args...) if err != nil { errors.LogError(r, err, "Failed to update user profile") errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) @@ -3757,14 +3790,25 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa } // Audit log + metadata := make(map[string]interface{}) + if req.DisplayName != nil { + metadata["displayName"] = *req.DisplayName + } + if req.Email != nil { + metadata["email"] = *req.Email + } + if req.AvatarURL != nil { + metadata["avatarUrl"] = *req.AvatarURL + } + if req.BlurHash != nil { + metadata["blurHash"] = *req.BlurHash + } + auditLogger.Log(r.Context(), audit.Entry{ - UserID: &userID, - Action: "profile_update", - Success: true, - Metadata: map[string]interface{}{ - "displayName": req.DisplayName, - "email": req.Email, - }, + UserID: &userID, + Action: "profile_update", + Success: true, + Metadata: metadata, }) w.WriteHeader(http.StatusOK)