Skip to content

feat(desktop): rework account settings with editable profile#944

Merged
saddlepaddle merged 1 commit intomainfrom
user-settings
Jan 25, 2026
Merged

feat(desktop): rework account settings with editable profile#944
saddlepaddle merged 1 commit intomainfrom
user-settings

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Jan 25, 2026

Summary

  • Add updateProfile and uploadAvatar mutations to user router for editing user profile
  • Remove version section from account settings page
  • Add editable name field with onBlur save functionality
  • Add avatar upload with hover edit overlay (matches organization settings pattern)
  • Use Electric SQL collections for real-time data sync instead of session refetch

Test plan

  • Navigate to Settings → Account
  • Verify version section is removed
  • Click avatar → file picker opens → select image → avatar updates
  • Edit name field → blur → name persists (doesn't revert)
  • Sign out and back in → changes persist

Summary by CodeRabbit

Release Notes

  • New Features

    • Added avatar upload functionality with file picker and live preview.
    • Added editable name field in account settings.
    • Added email display in user profile.
  • Improvements

    • Improved loading state UI during account data fetch.
    • Enhanced error handling with toast notifications for profile updates.
  • Removed

    • Removed version information from account settings.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

The changes introduce client-side user data management in the AccountSettings component using live queries, replace static UI with dynamic profile editing for name and avatar uploads, add two new TRPC endpoints for profile and avatar updates with validation and blob storage integration, and remove the version display feature from settings search.

Changes

Cohort / File(s) Summary
Account Settings UI
apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx, apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/components/ProfileSkeleton/*
Refactored AccountSettings to fetch user data via client-side live query instead of session reference; replaced Skeleton-based loading with ProfileSkeleton component. Added local state for name editing and avatar preview with onBlur persistence. Implemented avatar upload flow with file picker, image upload, and success/error handling via toast notifications. Replaced static copy/version UI with editable name field and read-only email display. Expanded imports to include Card, Input, useLiveQuery, useState, and new ProfileSkeleton component.
TRPC User Router
packages/trpc/src/router/user/user.ts
Added updateProfile endpoint to update user name with validation (1-100 chars). Added uploadAvatar endpoint with comprehensive logic: validates user existence, enforces MIME type restrictions, decodes base64 input, validates max file size (4.5MB), deletes old avatar from blob storage, uploads new avatar, updates user.image URL, and returns success response with error handling for missing user, invalid type, and oversized files.
Settings Search Configuration
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
Removed ACCOUNT_VERSION entry from SETTING_ITEM_ID constant and corresponding SETTINGS_ITEMS definition (title "Version", description, keywords).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant React as AccountSettings Component
    participant LiveQuery as Live Query
    participant API as TRPC API
    participant Blob as Blob Storage
    participant DB as Database

    User->>React: Load Account Settings
    React->>LiveQuery: Subscribe to user data
    LiveQuery->>DB: Fetch current user
    DB-->>LiveQuery: Return user data
    LiveQuery-->>React: Emit user snapshot
    React->>React: Render profile with name/avatar

    User->>React: Edit name & blur
    React->>API: Call updateProfile({ name })
    API->>DB: Update user name
    DB-->>API: Return updated user
    API-->>React: Return success + updated user
    React->>React: Show toast confirmation

    User->>React: Click upload avatar
    React->>React: Open file picker
    User->>React: Select image
    React->>React: Encode to base64
    React->>API: Call uploadAvatar({ fileData, fileName, mimeType })
    API->>API: Validate MIME type & size
    API->>Blob: Delete old avatar (if exists)
    API->>Blob: Upload new image
    Blob-->>API: Return blob URL
    API->>DB: Update user.image with blob URL
    DB-->>API: Return updated user
    API-->>React: Return success + new URL
    React->>React: Update avatar preview + show toast
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A hop, skip, and jump through profiles neat,
Where avatars upload and name updates beat,
Live queries dance, while blobs store with care,
The settings now sparkle with edits to spare! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly summarizes the main change: adding editable profile functionality to the account settings component.
Description check ✅ Passed The description covers key changes with a summary and test plan, though the description template sections (Related Issues, Type of Change, Testing details, Screenshots) are not all completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 25, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

- Add updateProfile and uploadAvatar mutations to user router
- Remove version section from account settings
- Add editable name field with onBlur save
- Add avatar upload with hover edit overlay
- Use Electric SQL collections for real-time data sync
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/trpc/src/router/user/user.ts`:
- Around line 101-124: The upload flow can leave an orphaned blob if put(...)
succeeds but the subsequent db.update(users)...where(...) fails; after calling
put(pathname, buffer, ... ) capture the blob result and, if the DB update
throws, call the storage deletion routine for that blob (use the same identifier
you passed to put — e.g., pathname or blob.url) to remove the uploaded file, log
any deletion errors without masking the original DB error, and then rethrow the
TRPCError as currently done; update the try/catch around put and db.update to
perform this cleanup using the put return value (blob) and ensure deletion
failures are handled separately.
🧹 Nitpick comments (4)
packages/trpc/src/router/user/user.ts (1)

68-87: Extract magic values to named constants.

Per coding guidelines, magic numbers and hardcoded values should be extracted to named constants at module top for clarity and maintainability.

Suggested refactor
+const ALLOWED_AVATAR_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"];
+const MAX_AVATAR_SIZE_MB = 4.5;
+
 export const userRouter = {

Then update the usage:

-			const allowedMimeTypes = ["image/png", "image/jpeg", "image/webp"];
-			if (!allowedMimeTypes.includes(input.mimeType)) {
+			if (!ALLOWED_AVATAR_MIME_TYPES.includes(input.mimeType)) {
-			if (sizeInMB > 4.5) {
+			if (sizeInMB > MAX_AVATAR_SIZE_MB) {
 				throw new TRPCError({
 					code: "BAD_REQUEST",
-					message: `File too large (${sizeInMB.toFixed(2)}MB). Maximum size is 4.5MB`,
+					message: `File too large (${sizeInMB.toFixed(2)}MB). Maximum size is ${MAX_AVATAR_SIZE_MB}MB`,
apps/desktop/src/renderer/routes/_authenticated/settings/account/components/AccountSettings/AccountSettings.tsx (3)

41-46: Consider filtering in the query instead of post-fetch.

The current approach fetches all users and filters client-side. If the collection grows, this could become inefficient. Consider filtering by currentUserId in the query if the live query API supports it.


79-81: Log errors with context before showing toast.

Per coding guidelines, errors should not be swallowed silently. Add logging with context for debugging.

Suggested fix
-		} catch {
+		} catch (error) {
+			console.error("[AccountSettings/handleAvatarUpload] Failed:", error);
 			toast.error("Failed to update avatar");
 		}

95-98: Log errors with context before showing toast.

Same as above - add error logging for observability.

Suggested fix
-		} catch {
+		} catch (error) {
+			console.error("[AccountSettings/handleNameBlur] Failed:", error);
 			toast.error("Failed to update name");
 			setNameValue(user.name ?? "");
 		}

Comment on lines +101 to +124
try {
const blob = await put(pathname, buffer, {
access: "public",
contentType: input.mimeType,
});

const [updatedUser] = await db
.update(users)
.set({ image: blob.url })
.where(eq(users.id, userId))
.returning();

return {
success: true,
url: blob.url,
user: updatedUser,
};
} catch (error) {
console.error("[user/uploadAvatar] Upload failed:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to upload avatar",
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential orphan blob if DB update fails.

If put() succeeds but the subsequent db.update() fails, the uploaded blob remains in storage without being referenced. Consider cleaning up the blob on DB failure to prevent storage leaks.

Suggested fix
 			try {
 				const blob = await put(pathname, buffer, {
 					access: "public",
 					contentType: input.mimeType,
 				});

-				const [updatedUser] = await db
-					.update(users)
-					.set({ image: blob.url })
-					.where(eq(users.id, userId))
-					.returning();
+				let updatedUser;
+				try {
+					[updatedUser] = await db
+						.update(users)
+						.set({ image: blob.url })
+						.where(eq(users.id, userId))
+						.returning();
+				} catch (dbError) {
+					// Clean up orphan blob
+					await del(blob.url).catch(() => {});
+					throw dbError;
+				}

 				return {
 					success: true,
 					url: blob.url,
 					user: updatedUser,
 				};
🤖 Prompt for AI Agents
In `@packages/trpc/src/router/user/user.ts` around lines 101 - 124, The upload
flow can leave an orphaned blob if put(...) succeeds but the subsequent
db.update(users)...where(...) fails; after calling put(pathname, buffer, ... )
capture the blob result and, if the DB update throws, call the storage deletion
routine for that blob (use the same identifier you passed to put — e.g.,
pathname or blob.url) to remove the uploaded file, log any deletion errors
without masking the original DB error, and then rethrow the TRPCError as
currently done; update the try/catch around put and db.update to perform this
cleanup using the put return value (blob) and ensure deletion failures are
handled separately.

@saddlepaddle saddlepaddle merged commit 11c52a0 into main Jan 25, 2026
13 checks passed
@Kitenite Kitenite deleted the user-settings branch January 26, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant