Skip to content

Conversation

richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Oct 17, 2025

Introduces server actions for uploading and removing user profile images, updates account settings UI to allow image management, and refactors all avatar displays to support showing user profile images. Also propagates author images through comments and related components, ensuring consistent avatar rendering across the app.

Summary by CodeRabbit

  • New Features
    • Profile image management: upload and remove your profile picture from Account Settings.
    • Uploads accept PNG/JPEG up to 3MB and update your avatar immediately in the UI.
    • Comments and various list views now show author profile images alongside names for consistent identification.

Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds profile image upload and removal server actions, integrates profile image management in the Settings UI with FileInput, extends comment types to include authorImage, and centralizes avatar rendering by adding an imageUrl prop to the Avatar component across multiple UI areas.

Changes

Cohort / File(s) Change Summary
Profile image server actions
apps/web/actions/account/upload-profile-image.ts, apps/web/actions/account/remove-profile-image.ts
New server actions: uploadProfileImage(formData) validates file, uploads to S3, updates DB, and revalidates caches; removeProfileImage() clears user image and revalidates caches.
Settings — profile image UI
apps/web/app/(org)/dashboard/settings/account/Settings.tsx
Added profile image preview/upload/remove flows, mutations wired to new server actions, and FileInput integration with upload/remove handlers and composite loading state.
File input refactor
apps/web/components/FileInput.tsx
Introduced ALLOWED_IMAGE_TYPES, MAX_FILE_SIZE_BYTES, removed selectedFile state, unified preview handling, and simplified validation/conditional UI.
Avatar component
packages/ui/src/components/Avatar.tsx
AvatarProps gained `imageUrl?: string
Dashboard avatar usage
apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx, apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx, apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
Replaced conditional Image/Avatar branches with unified Avatar using imageUrl, simplifying avatar rendering across nav and member lists.
Video sharing — comment types
apps/web/app/s/[videoId]/Share.tsx, apps/web/app/s/[videoId]/_components/ShareVideo.tsx, apps/web/app/s/[videoId]/_components/Sidebar.tsx
Added optional `authorImage: string
Video sharing — components
apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx, apps/web/app/s/[videoId]/_components/CommentStamp.tsx, apps/web/app/s/[videoId]/_components/ShareHeader.tsx, apps/web/app/s/[videoId]/_components/Toolbar.tsx, apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx, apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
Components updated to pass authorImage via Avatar's imageUrl prop; removed conditional Next/Image usage in favor of Avatar where applicable.
Comment action & queries
apps/web/actions/videos/new-comment.ts, apps/web/app/s/[videoId]/page.tsx
Server action and comments query now include authorImage: user.image ?? null in comment payloads and projections.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser (UI)
    participant SettingsUI as Settings UI
    participant Server as Server Action
    participant S3 as S3
    participant DB as Database
    participant Cache as Next.js Cache

    Browser->>SettingsUI: select file / click remove
    SettingsUI->>SettingsUI: validate file / show preview
    SettingsUI->>Server: uploadProfileImage(FormData) or removeProfileImage()
    Server->>Server: auth check, validate file, generate key
    Server->>S3: upload file
    S3-->>Server: return URL
    Server->>DB: update user.image (set URL or null)
    DB-->>Server: ack
    Server->>Cache: revalidate /dashboard/settings/account & /dashboard/layout
    Cache-->>Server: ack
    Server-->>SettingsUI: { success, imageUrl? }
    SettingsUI-->>Browser: update preview / UI state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Brendonovich

Poem

🐰 Hop, hop — a pixel tale I bring,

New avatars ready to gleam and sing,
Uploads, removals, S3 in flight,
Comments now wear faces bright,
I nibble carrots and cheer this sight! 🥕✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: Add profile image upload and display support" accurately reflects the primary objective and main changes in the pull request. The PR introduces server-side actions for uploading and removing profile images, updates the account settings UI to provide image management functionality, and refactors the Avatar component and related rendering logic across the application to display user profile images. While supporting changes include comment author image propagation and FileInput component improvements, these all serve the central goal stated in the title. The title is concise, specific, and clearly communicates the feature without using vague language or unnecessary noise.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 087886e and 62929e1.

📒 Files selected for processing (5)
  • apps/web/actions/account/remove-profile-image.ts (1 hunks)
  • apps/web/actions/account/upload-profile-image.ts (1 hunks)
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx (5 hunks)
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx (2 hunks)
  • apps/web/components/FileInput.tsx (5 hunks)

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.

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/s/[videoId]/_components/ShareVideo.tsx (1)

200-206: Add missing authorImage field to comment mapping.

The authorImage field is missing from the comment objects passed to CapVideoPlayer, even though the component expects it (line 53 of CapVideoPlayer.tsx). This prevents author images from being displayed in comment stamps.

Apply this diff to include the authorImage field:

 comments={commentsData.map((comment) => ({
   id: comment.id,
   type: comment.type,
   timestamp: comment.timestamp,
   content: comment.content,
   authorName: comment.authorName,
+  authorImage: comment.authorImage,
 }))}
🧹 Nitpick comments (4)
apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx (1)

272-277: LGTM! Consider simplifying the nullish coalescing.

The refactoring to use the Avatar component consistently with the imageUrl prop is well-executed and aligns with the pattern adopted across the codebase. This simplifies the rendering logic by eliminating conditional branches.

The ?? undefined on line 275 is redundant. Since both null and undefined are falsy and the Avatar component checks if (imageUrl), you can simplify this:

 <Avatar
   letterClass="text-xs lg:text-md"
   name={user.name ?? "User"}
-  imageUrl={user.image ?? undefined}
+  imageUrl={user.image}
   className="flex-shrink-0 size-[24px] text-gray-12"
 />

If user.image is typed as string | null and TypeScript requires explicit type conversion, you could also use:

-  imageUrl={user.image ?? undefined}
+  imageUrl={user.image || undefined}
apps/web/actions/account/upload-profile-image.ts (2)

49-49: Consider removing unnecessary sanitizeFile call for images.

The sanitizeFile utility (from apps/web/lib/sanitizeFile.ts) only performs sanitization for SVG files and returns non-SVG files unchanged. Since this action only accepts PNG and JPEG images (enforced at lines 38-40), the sanitization step is effectively a no-op that adds unnecessary overhead.

Apply this diff to remove the redundant call:

-    const sanitizedFile = await sanitizeFile(file);
     let imageUrl: string | undefined;

     await Effect.gen(function* () {
       const [bucket] = yield* S3Buckets.getBucketAccess(Option.none());

       const bodyBytes = yield* Effect.promise(async () => {
-        const buf = await sanitizedFile.arrayBuffer();
+        const buf = await file.arrayBuffer();
         return new Uint8Array(buf);
       });

       yield* bucket.putObject(fileKey, bodyBytes, {
         contentType: file.type,
       });

75-77: Simplify imageUrl validation logic.

The check typeof imageUrl !== "string" || imageUrl.length === 0 is redundant since imageUrl is initialized as undefined and only assigned a string value within the Effect workflow. If the workflow completes successfully, imageUrl will always be a non-empty string.

Apply this diff to simplify:

-    if (typeof imageUrl !== "string" || imageUrl.length === 0) {
+    if (!imageUrl) {
       throw new Error("Failed to resolve uploaded profile image URL");
     }
-
-    const finalImageUrl = imageUrl;

     await db()
       .update(users)
-      .set({ image: finalImageUrl })
+      .set({ image: imageUrl })
       .where(eq(users.id, user.id));

     revalidatePath("/dashboard/settings/account");
     revalidatePath("/dashboard", "layout");

-    return { success: true, imageUrl: finalImageUrl } as const;
+    return { success: true, imageUrl } as const;
packages/ui/src/components/Avatar.tsx (1)

78-88: Consider adding error handling for broken images.

The <img> element could fail to load if the URL is invalid or the image is unavailable. Consider adding an onError handler to fallback to the initials rendering.

Apply this diff to add error handling:

 export const Avatar: React.FC<AvatarProps> = ({
 	name,
 	imageUrl,
 	className = "",
 	letterClass = "text-xs",
 }) => {
+	const [imageError, setImageError] = useState(false);
+
-	if (imageUrl) {
+	if (imageUrl && !imageError) {
 		return (
 			<img
 				src={imageUrl}
 				alt={name ?? "Avatar"}
 				className={clsx("rounded-full object-cover size-4", className)}
 				loading="lazy"
 				referrerPolicy="no-referrer"
+				onError={() => setImageError(true)}
 			/>
 		);
 	}

Note: You'll need to import useState from React at the top of the file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 548d5ba and 087886e.

📒 Files selected for processing (19)
  • apps/web/actions/account/remove-profile-image.ts (1 hunks)
  • apps/web/actions/account/upload-profile-image.ts (1 hunks)
  • apps/web/actions/videos/new-comment.ts (1 hunks)
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx (1 hunks)
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx (5 hunks)
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx (2 hunks)
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx (1 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (2 hunks)
  • apps/web/app/s/[videoId]/page.tsx (1 hunks)
  • apps/web/components/FileInput.tsx (5 hunks)
  • packages/ui/src/components/Avatar.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/actions/videos/new-comment.ts
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/web/actions/account/upload-profile-image.ts
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/actions/account/remove-profile-image.ts
  • packages/ui/src/components/Avatar.tsx
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/components/FileInput.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/actions/videos/new-comment.ts
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/web/actions/account/upload-profile-image.ts
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/actions/account/remove-profile-image.ts
  • packages/ui/src/components/Avatar.tsx
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/components/FileInput.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/actions/videos/new-comment.ts
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/web/actions/account/upload-profile-image.ts
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/actions/account/remove-profile-image.ts
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/components/FileInput.tsx
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All Groq/OpenAI calls must execute in Next.js Server Actions under apps/web/actions; do not invoke AI providers elsewhere

Files:

  • apps/web/actions/videos/new-comment.ts
  • apps/web/actions/account/upload-profile-image.ts
  • apps/web/actions/account/remove-profile-image.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/actions/videos/new-comment.ts
  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/web/actions/account/upload-profile-image.ts
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/actions/account/remove-profile-image.ts
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/components/FileInput.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/dashboard/settings/account/Settings.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx
  • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/CommentStamp.tsx
  • apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
packages/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Component files in packages/ui should use PascalCase naming if they define React/Solid components.

Files:

  • packages/ui/src/components/Avatar.tsx
🧬 Code graph analysis (9)
apps/web/app/(org)/dashboard/settings/account/Settings.tsx (3)
apps/web/actions/account/upload-profile-image.ts (1)
  • uploadProfileImage (22-94)
apps/web/actions/account/remove-profile-image.ts (1)
  • removeProfileImage (9-25)
apps/web/components/FileInput.tsx (1)
  • FileInput (38-283)
apps/web/app/s/[videoId]/_components/ShareHeader.tsx (1)
packages/ui/src/components/Avatar.tsx (1)
  • Avatar (72-108)
apps/web/actions/account/upload-profile-image.ts (4)
apps/web/lib/sanitizeFile.ts (1)
  • sanitizeFile (4-17)
apps/web/lib/server.ts (1)
  • runPromise (123-135)
packages/database/index.ts (1)
  • db (18-25)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx (1)
packages/ui/src/components/Avatar.tsx (1)
  • Avatar (72-108)
apps/web/app/s/[videoId]/page.tsx (1)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/actions/account/remove-profile-image.ts (2)
packages/database/index.ts (1)
  • db (18-25)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx (1)
packages/ui/src/components/Avatar.tsx (1)
  • Avatar (72-108)
apps/web/app/s/[videoId]/_components/CommentStamp.tsx (1)
packages/ui/src/components/Avatar.tsx (1)
  • Avatar (72-108)
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx (1)
packages/ui/src/components/Avatar.tsx (1)
  • Avatar (72-108)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (19)
apps/web/actions/videos/new-comment.ts (1)

70-70: LGTM!

The addition of authorImage to the comment response payload aligns with the PR's objective to propagate author images throughout the application.

apps/web/actions/account/upload-profile-image.ts (1)

22-93: LGTM with optional improvements!

The implementation correctly handles:

  • Authentication and authorization
  • File type and size validation
  • S3 upload with proper key generation
  • Multiple environment configurations for URL construction
  • Database updates and cache revalidation
  • Error handling and reporting

The function follows the coding guidelines for server actions and Effect usage.

apps/web/app/s/[videoId]/_components/tabs/Activity/Comment.tsx (1)

81-81: LGTM!

The addition of imageUrl={comment.authorImage ?? undefined} correctly propagates the author's image to the Avatar component, enabling avatar rendering with custom images.

apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx (1)

53-53: LGTM!

The addition of the optional authorImage field to the comment type in Props correctly extends the data shape to support avatar rendering with custom images.

apps/web/app/s/[videoId]/page.tsx (1)

664-664: LGTM!

The addition of authorImage: users.image to the comment query projection correctly fetches the author's profile image alongside other comment data, enabling avatar rendering throughout the application.

apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1)

91-91: LGTM!

The addition of authorImage: user?.image ?? null to both optimisticComment and optimisticReply ensures that optimistic UI updates correctly display author avatars during the pending state before server confirmation.

Also applies to: 134-134

apps/web/actions/account/remove-profile-image.ts (1)

9-24: LGTM!

The implementation correctly handles profile image removal by:

  • Verifying user authentication
  • Updating the database to set the image field to null
  • Triggering cache revalidation for affected paths
  • Returning a consistent success response
apps/web/app/s/[videoId]/_components/ShareVideo.tsx (1)

32-32: LGTM!

The addition of authorImage: string | null to the CommentWithAuthor type correctly extends the data shape to include the author's profile image.

apps/web/app/s/[videoId]/_components/ShareHeader.tsx (1)

240-245: LGTM! Unified avatar rendering.

The refactor to use a single Avatar component with imageUrl prop simplifies the rendering logic and aligns with the broader architectural changes in this PR.

apps/web/app/s/[videoId]/Share.tsx (1)

26-35: LGTM! Type extensions are consistent.

The addition of authorImage fields to both CommentWithAuthor and CommentType properly extends the comment data model to support author profile images. The nullable typing is appropriate for optional user images.

apps/web/app/s/[videoId]/_components/CommentStamp.tsx (2)

5-13: LGTM! Type extension is consistent.

The addition of authorImage?: string | null to the comment interface properly supports displaying author profile images in comment stamps.


64-70: LGTM! Consistent avatar rendering.

The Avatar component usage with imageUrl={comment.authorImage ?? undefined} aligns with the unified rendering approach across the application.

apps/web/app/s/[videoId]/_components/Toolbar.tsx (2)

39-52: Verify the authorName change on Line 42.

Line 42 changes authorName from Comment.CommentId.make(...) to user?.name || "Anonymous". This appears to be a bug fix (using CommentId for a name seems incorrect), but it's not mentioned in the AI-generated summary.

Please confirm this is an intentional fix. If so, consider mentioning it in the PR description as it's a correctness improvement beyond just adding profile images.

Additionally, the authorImage addition on Line 43 is consistent with the PR objectives.


81-94: LGTM! Consistent optimistic comment creation.

The addition of authorImage: user?.image ?? null to the optimistic text comment is consistent with the emoji comment handling and the broader PR changes.

apps/web/app/s/[videoId]/_components/Sidebar.tsx (1)

16-19: LGTM! Type extension is consistent.

The addition of authorImage?: string | null to the local CommentType definition maintains consistency with the comment type extensions throughout the application.

apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MemberSelect.tsx (2)

189-206: LGTM! Simplified avatar rendering.

The refactor to use Avatar with imageUrl={opt.image} for all dropdown options eliminates conditional rendering and aligns with the unified avatar approach across the application.


216-246: LGTM! Consistent selected member rendering.

The Avatar usage with imageUrl={tag.image} for selected members matches the dropdown options pattern and maintains consistency across the component.

apps/web/app/(org)/dashboard/spaces/[spaceId]/components/MembersIndicator.tsx (1)

163-178: LGTM! Unified member avatar rendering.

The refactor to use Avatar with imageUrl={member.image || undefined} simplifies the rendering logic and aligns with the consistent avatar approach throughout the PR.

packages/ui/src/components/Avatar.tsx (1)

67-67: ****

The review comment's concern about validating imageUrl for "arbitrary strings from user data" is not supported by the codebase.

The imageUrl values come from controlled server-side sources:

  • User/comment/member records in the database (e.g., data.ownerImage, comment.authorImage, member.image)
  • These are populated via upload-profile-image.ts, which sanitizes files server-side before generating S3 URLs
  • URLs are constructed deterministically from trusted AWS configuration (CAP_AWS_BUCKET_URL, CAP_AWS_ENDPOINT, or S3 hostname) with sanitized file keys

This is a controlled server-side flow, not arbitrary untrusted input. The referrerPolicy="no-referrer" is already in place for privacy. URL validation, CSP, or CDN proxying are not necessary for this use case.

Likely an incorrect or invalid review comment.

@richiemcilroy richiemcilroy merged commit 19d9ce1 into main Oct 17, 2025
3 of 4 checks passed
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