Skip to content

Conversation

chiol
Copy link
Contributor

@chiol chiol commented Aug 29, 2025

Summary by CodeRabbit

  • New Features
    • Added FeedbackImage for secure, seamless image display using presigned URLs when available.
    • ImagePreviewButton now supports an initialIndex prop to open the gallery at a specific image with synced thumbnails/navigation.
  • Refactor
    • Replaced direct image rendering with FeedbackImage in image preview and sheet detail table for consistent behavior.
    • Consolidated image URL handling and removed legacy presigned URL logic from UI components.
  • Exports
    • Exposed FeedbackImage via the shared UI index for reuse.

Copy link

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds a new FeedbackImage React component that conditionally fetches presigned image URLs based on channel configuration and router-derived IDs. Refactors image-preview-button and sheet-detail-table to use FeedbackImage. Updates shared UI index to export FeedbackImage. Introduces optional initialIndex prop to image-preview-button.

Changes

Cohort / File(s) Summary of edits
New image component
apps/web/src/shared/ui/feedback-image.tsx
Added FeedbackImage component. Derives projectId/channelId from router, fetches channel config, computes imageKey from input URL, conditionally fetches presigned URL, renders Next.js Image using presigned URL when available.
Consumers updated to use FeedbackImage
apps/web/src/shared/ui/image-preview-button.tsx, apps/web/src/shared/ui/sheet-detail-table.ui.tsx
Replaced previous image rendering with FeedbackImage. Removed prior presigned-URL logic from image-preview-button. Added optional initialIndex prop and Swiper thumbs synchronization. Adjusted imports accordingly.
Shared UI index export
apps/web/src/shared/ui/index.ts
Re-exported FeedbackImage from ./feedback-image.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Component as FeedbackImage
  participant Router
  participant OAI as OAI Client
  participant API as Admin API

  User->>Component: Render with url
  Component->>Router: Read query (projectId, channelId)
  Router-->>Component: IDs (when ready)

  rect rgba(230,240,255,0.5)
    Component->>OAI: GET /api/admin/projects/{projectId}/channels/{channelId}
    OAI->>API: Fetch channel data
    API-->>OAI: channelData (imageConfig)
    OAI-->>Component: channelData
  end

  Component->>Component: Compute imageKey from url
  alt enablePresignedUrlDownload && imageKey
    rect rgba(230,255,230,0.5)
      Component->>OAI: GET /api/admin/projects/{projectId}/channels/{channelId}/image-download-url?imageKey=...
      OAI->>API: Request presigned URL
      API-->>OAI: presignedUrl
      OAI-->>Component: presignedUrl
      Component-->>User: Render Image src=presignedUrl
    end
  else
    Component-->>User: Render Image src=url
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • jihun
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/feedback-image-presigned-url

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@chiol chiol requested review from Copilot and jihun August 29, 2025 08:27
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new FeedbackImage component to handle image rendering with presigned URL support and integrates it across image display components to replace inline image logic.

  • Extracts image rendering logic with presigned URL handling into a reusable FeedbackImage component
  • Replaces direct Image usage and inline PresignedURLImage component in SheetDetailTable and ImagePreviewButton
  • Exports the new component from the UI module index

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
apps/web/src/shared/ui/feedback-image.tsx New component that handles image rendering with presigned URL logic
apps/web/src/shared/ui/image-preview-button.tsx Removes inline PresignedURLImage component and uses FeedbackImage instead
apps/web/src/shared/ui/sheet-detail-table.ui.tsx Replaces direct Image usage with FeedbackImage component
apps/web/src/shared/ui/index.ts Exports the new FeedbackImage component

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

@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: 3

🧹 Nitpick comments (4)
apps/web/src/shared/ui/feedback-image.tsx (1)

66-74: Broaden S3 host detection (support accelerate/region/virtual-host styles).

Use a more robust check:

-    const host = parsed.hostname;
-    const parts = key.split('/', 2);
-    if (
-      (host === 's3.amazonaws.com' || host.startsWith('s3.')) &&
-      parts.length >= 2
-    ) {
+    const host = parsed.hostname;
+    const parts = key.split('/', 2);
+    const isS3StyleHost =
+      host === 's3.amazonaws.com' ||
+      host.endsWith('.s3.amazonaws.com') ||
+      /^s3[.-][a-z0-9-]+\.amazonaws\.com$/i.test(host) ||
+      host === 's3-accelerate.amazonaws.com';
+    if (isS3StyleHost && parts.length >= 2) {
       return parts.slice(1).join('/');
     }
apps/web/src/shared/ui/sheet-detail-table.ui.tsx (2)

198-205: Pass a concrete sizes hint to reduce image payloads for 64px tiles.

-              <FeedbackImage url={v} />
+              <FeedbackImage url={v} sizes="64px" alt="" />

360-367: Same here for edit mode thumbnails.

-              <FeedbackImage url={v} />
+              <FeedbackImage url={v} sizes="64px" alt="" />
apps/web/src/shared/ui/image-preview-button.tsx (1)

90-96: Provide tight sizes for thumbnails.

-            {urls.map((url) => (
+            {urls.map((url, idx) => (
-              <SwiperSlide
-                key={url}
+              <SwiperSlide
+                key={`${url}-thumb-${idx}`}
                 className="rounded-8 bg-neutral-secondary relative overflow-hidden"
               >
-                <FeedbackImage url={url} />
+                <FeedbackImage url={url} sizes="64px" alt="" />
               </SwiperSlide>
             ))}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 36a51a1 and 420db86.

📒 Files selected for processing (4)
  • apps/web/src/shared/ui/feedback-image.tsx (1 hunks)
  • apps/web/src/shared/ui/image-preview-button.tsx (4 hunks)
  • apps/web/src/shared/ui/index.ts (1 hunks)
  • apps/web/src/shared/ui/sheet-detail-table.ui.tsx (3 hunks)
⏰ 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). (1)
  • GitHub Check: integration-test
🔇 Additional comments (4)
apps/web/src/shared/ui/index.ts (1)

54-55: LGTM — exporting FeedbackImage from shared UI.

apps/web/src/shared/ui/sheet-detail-table.ui.tsx (1)

41-41: Import looks good.

apps/web/src/shared/ui/image-preview-button.tsx (2)

17-17: OK to add state/effects for thumbs + initial index.


34-35: Good: centralizing image logic via FeedbackImage.

Comment on lines +22 to +26
interface Props {
url: string;
}

const FeedbackImage = ({ url }: Props) => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expose alt/sizes/className to fix a11y and prevent over-fetching with next/image fill.

Add optional props and forward them. Consumers (thumbnails/modals) can pass correct sizes.

-interface Props {
-  url: string;
-}
+interface Props {
+  url: string;
+  alt?: string;
+  sizes?: string;
+  className?: string;
+}

-const FeedbackImage = ({ url }: Props) => {
+const FeedbackImage = ({ url, alt, sizes, className }: Props) => {
@@
   return (
     <Image
-      src={presignedUrl ?? url}
-      alt={presignedUrl ?? url}
-      className="cursor-pointer object-cover"
-      fill
+      src={presignedUrl ?? url}
+      alt={alt ?? ''}
+      className={`cursor-pointer object-cover ${className ?? ''}`}
+      fill
+      sizes={sizes}
     />
   );

Also applies to: 88-93

🤖 Prompt for AI Agents
In apps/web/src/shared/ui/feedback-image.tsx around lines 22-26 (and also apply
same changes at 88-93), the component only accepts url which prevents setting
alt for accessibility and sizes/className for next/image fill behavior; update
the Props type to include optional alt?: string, sizes?: string, and className?:
string, update the component signature to accept and forward those props
(destructure { url, alt, sizes, className }: Props), and pass alt, sizes, and
className through to the underlying Image element so consumers
(thumbnails/modals) can supply appropriate values to fix a11y and avoid
over-fetching. Ensure Typescript types reflect optional props and no other logic
changes are required.

Comment on lines +42 to +75
const imageKey = useMemo(() => {
if (!channelData?.imageConfig?.enablePresignedUrlDownload) return url;

let parsed: URL | null = null;
try {
parsed = new URL(
url,
typeof window !== 'undefined' ?
window.location.href
: 'http://localhost',
);
} catch {
return url;
}
if (!/^https?:$/.test(parsed.protocol)) return url;

const rawPath = parsed.pathname.replace(/^\/+/, '');
let key = rawPath;
try {
key = decodeURIComponent(rawPath);
} catch {
/* keep raw */
}

const host = parsed.hostname;
const parts = key.split('/', 2);
if (
(host === 's3.amazonaws.com' || host.startsWith('s3.')) &&
parts.length >= 2
) {
return parts.slice(1).join('/');
}
return key;
}, [channelData, url]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t hit the presign endpoint when the URL is not a parsable HTTP(S) object.

When parsing fails or protocol isn’t http/https, imageKey currently falls back to the original url, which still enables the presign query and will send an invalid key. Return a falsy value and hard‑gate the query instead.

Apply:

-  const imageKey = useMemo(() => {
-    if (!channelData?.imageConfig?.enablePresignedUrlDownload) return url;
+  const imageKey = useMemo(() => {
+    if (!channelData?.imageConfig?.enablePresignedUrlDownload) return undefined;
@@
-    } catch {
-      return url;
+    } catch {
+      return undefined;
     }
-    if (!/^https?:$/.test(parsed.protocol)) return url;
+    if (!/^https?:$/.test(parsed.protocol)) return undefined;
@@
-  const { data: presignedUrl } = useOAIQuery({
+  const { data: presignedUrl } = useOAIQuery({
     path: '/api/admin/projects/{projectId}/channels/{channelId}/image-download-url',
     variables: { channelId, projectId, imageKey },
     queryOptions: {
-      enabled: Boolean(
-        imageKey && channelData?.imageConfig?.enablePresignedUrlDownload,
-      ),
+      enabled: Boolean(
+        imageKey &&
+        channelData?.imageConfig?.enablePresignedUrlDownload &&
+        router.isReady &&
+        Number.isFinite(projectId) &&
+        Number.isFinite(channelId)
+      ),
     },
   });

Also applies to: 77-85

🤖 Prompt for AI Agents
In apps/web/src/shared/ui/feedback-image.tsx around lines 42 to 75 (and also
apply the same change to lines 77-85), imageKey currently returns the original
url when parsing fails or protocol isn't http/https which still triggers the
presign query with an invalid key; change the logic so that when URL parsing
fails or the protocol check fails you return a falsy value (e.g. null) instead
of the original url, and ensure callers/queries hard‑gate on a truthy imageKey
before calling the presign endpoint so presign is only invoked for valid parsed
HTTP(S) keys.

Comment on lines 75 to 78
{urls.map((url) => (
<SwiperSlide key={url} className="relative">
<PresignedURLImage url={url} />
<FeedbackImage url={url} />
</SwiperSlide>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Provide sizes for the main slide to avoid 100vw default and oversized requests. Also mind duplicate keys if urls aren’t unique.

-            {urls.map((url) => (
+            {urls.map((url, idx) => (
-              <SwiperSlide key={url} className="relative">
-                <FeedbackImage url={url} />
+              <SwiperSlide key={`${url}-${idx}`} className="relative">
+                <FeedbackImage url={url} sizes="100vw" alt="" />
               </SwiperSlide>
             ))}

If urls are guaranteed unique, keeping key={url} is fine. Otherwise prefer the composite key above. Please confirm.


🏁 Script executed:

#!/usr/bin/env bash
# Locate the ImagePreviewButton file
fd image-preview-button.tsx -t f

# Show the top of the component to inspect props and imports
sed -n '1,200p' apps/web/src/shared/ui/image-preview-button.tsx

# Find where `urls` is declared or referenced
rg -n 'urls' -C3 apps/web/src/shared/ui/image-preview-button.tsx

# Find the import of FeedbackImage
rg -n 'FeedbackImage' -C3 apps/web/src/shared/ui/image-preview-button.tsx

# Locate the FeedbackImage component definition
fd FeedbackImage.tsx -t f

# Display its first 200 lines to inspect accepted props
sed -n '1,200p' "$(fd FeedbackImage.tsx -t f | head -n1)"

Length of output: 5667


🏁 Script executed:

#!/usr/bin/env bash
# List files in the shared UI directory
ls apps/web/src/shared/ui

# Identify the exact filename for FeedbackImage
ls apps/web/src/shared/ui | grep -i feedback-image

# Display the component definition to verify accepted props
sed -n '1,200p' apps/web/src/shared/ui/feedback-image.tsx

Length of output: 3615


Support responsive image sizing and stable keys
In FeedbackImage (apps/web/src/shared/ui/feedback-image.tsx), extend its Props to accept optional sizes?: string and alt?: string and forward them to Next/Image:

interface Props {
  url: string;
  alt?: string;
  sizes?: string;
}
// …
<Image
  src={presignedUrl ?? url}
  alt={alt ?? presignedUrl ?? url}
  sizes={sizes}
  className="cursor-pointer object-cover"
  fill
/>

Then in ImagePreviewButton (apps/web/src/shared/ui/image-preview-button.tsx) at lines 75–78, update the main slide mapping:

-{urls.map((url) => (
-  <SwiperSlide key={url} className="relative">
-    <FeedbackImage url={url} />
+{urls.map((url, idx) => (
+  <SwiperSlide key={`${url}-${idx}`} className="relative">
+    <FeedbackImage url={url} sizes="100vw" alt="" />

If urls are guaranteed unique, you can keep key={url}; otherwise use the composite key above.

🤖 Prompt for AI Agents
In apps/web/src/shared/ui/image-preview-button.tsx around lines 75 to 78, the
SwiperSlide mapping needs to pass optional alt and sizes through to
FeedbackImage and ensure stable keys; update the map to include alt and sizes
props (e.g., <FeedbackImage url={url} alt={optionalAlt} sizes={optionalSizes}
/>) and, if urls may not be unique, use a composite stable key (for example
`${url}-${index}`) instead of key={url}; also ensure FeedbackImage's Props are
extended to accept sizes?: string and alt?: string and those are forwarded to
the Next/Image component.

@chiol chiol merged commit 4e4fae0 into dev Aug 29, 2025
7 checks passed
@chiol chiol deleted the fix/feedback-image-presigned-url branch August 29, 2025 08:48
@coderabbitai coderabbitai bot mentioned this pull request Sep 2, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 16, 2025
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.

2 participants