diff --git a/b0esche_cloud/lib/services/api_client.dart b/b0esche_cloud/lib/services/api_client.dart index 9653355..aac1f5c 100644 --- a/b0esche_cloud/lib/services/api_client.dart +++ b/b0esche_cloud/lib/services/api_client.dart @@ -185,11 +185,9 @@ class ApiClient { Future> updateUserProfile({ required String displayName, String? email, - String? avatarUrl, }) async { final data = {'displayName': displayName}; if (email != null) data['email'] = email; - if (avatarUrl != null) data['avatarUrl'] = avatarUrl; return putRaw('/user/profile', data: data); } diff --git a/b0esche_cloud/lib/widgets/account_settings_dialog.dart b/b0esche_cloud/lib/widgets/account_settings_dialog.dart index 41d33ab..dc71936 100644 --- a/b0esche_cloud/lib/widgets/account_settings_dialog.dart +++ b/b0esche_cloud/lib/widgets/account_settings_dialog.dart @@ -24,6 +24,7 @@ class _AccountSettingsDialogState extends State { bool _isUploadingAvatar = false; double _avatarUploadProgress = 0.0; String? _error; + bool _hasChanges = false; // Profile fields late TextEditingController _displayNameController; @@ -40,6 +41,11 @@ class _AccountSettingsDialogState extends State { void initState() { super.initState(); _displayNameController = TextEditingController(); + _displayNameController.addListener(() { + if (mounted && _currentUser != null) { + setState(() => _hasChanges = _displayNameController.text != (_currentUser!.displayName ?? '')); + } + }); _currentPasswordController = TextEditingController(); _newPasswordController = TextEditingController(); _confirmPasswordController = TextEditingController(); @@ -52,6 +58,7 @@ class _AccountSettingsDialogState extends State { _currentUser = authState.user; _displayNameController.text = _currentUser?.displayName ?? ''; _avatarUrl = _currentUser?.avatarUrl; + _hasChanges = false; } } }); @@ -145,6 +152,11 @@ class _AccountSettingsDialogState extends State { } } + void _onSavePressed() { + if (!_hasChanges || _isLoading) return; + _updateProfile(); + } + Future _updateProfile() async { if (_currentUser == null) { return; @@ -155,7 +167,6 @@ class _AccountSettingsDialogState extends State { final apiClient = GetIt.I(); await apiClient.updateUserProfile( displayName: _displayNameController.text, - avatarUrl: _avatarUrl, ); final updatedUser = _currentUser!.copyWith( @@ -641,7 +652,7 @@ class _AccountSettingsDialogState extends State { child: SizedBox( width: 144, child: ModernGlassButton( - onPressed: () => _updateProfile(), + onPressed: _onSavePressed, isLoading: _isLoading, child: _isLoading ? const SizedBox( diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 2a46047..1a6562d 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -3872,7 +3872,6 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa var req struct { DisplayName *string `json:"displayName"` Email *string `json:"email"` - AvatarURL *string `json:"avatarUrl"` } if err = json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -3895,11 +3894,6 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa 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 len(setParts) == 0 { // No fields to update @@ -3927,9 +3921,6 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa if req.Email != nil { metadata["email"] = *req.Email } - if req.AvatarURL != nil { - metadata["avatarUrl"] = *req.AvatarURL - } auditLogger.Log(r.Context(), audit.Entry{ UserID: &userID, diff --git a/go_cloud/internal/storage/webdav.go b/go_cloud/internal/storage/webdav.go index d7133e7..490f98e 100644 --- a/go_cloud/internal/storage/webdav.go +++ b/go_cloud/internal/storage/webdav.go @@ -91,9 +91,11 @@ func (c *WebDAVClient) Upload(ctx context.Context, remotePath string, r io.Reade if c == nil { return fmt.Errorf("no webdav client configured") } - // Ensure parent collections - if err := c.ensureParent(ctx, remotePath); err != nil { - return err + // Ensure parent collections, skip for .avatars as it should exist + if !strings.HasPrefix(remotePath, ".avatars/") { + if err := c.ensureParent(ctx, remotePath); err != nil { + return err + } } // Construct URL // remotePath might be like /orgs//file.txt; ensure it joins to basePrefix