Skip to content

Fix PDP sale prices, variant image switching, and OG image - #97

Merged
damianlegawiec merged 5 commits into
mainfrom
fix/pdp-sale-prices-variant-images-og
Apr 2, 2026
Merged

Fix PDP sale prices, variant image switching, and OG image#97
damianlegawiec merged 5 commits into
mainfrom
fix/pdp-sale-prices-variant-images-og

Conversation

@Cichorek

@Cichorek Cichorek commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix strikethrough price not displaying on PDP and ProductCard when original_price is null (compare-at price sales)
  • Gallery now shows all product images and auto-switches to variant's assigned image on variant selection
  • Add variants.media to product page expand for variant image data from the API
  • Use single OG image proxied through Next.js image optimization with proper dimensions

Test plan

  • Open a product with compare-at price — verify strikethrough price and "Sale" badge appear on PDP and product cards
  • Open a product with variant-specific images — verify clicking a variant switches the main gallery image
  • Verify all product images remain visible in thumbnails regardless of selected variant
  • Check og:image meta tag — should be single image, proxied through /_next/image when STORE_URL is set

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Corrected product gallery image selection to properly highlight images associated with selected variants
    • Fixed strikethrough price display logic for sale items to show accurate compare prices
  • Improvements

    • Enhanced social media sharing with improved Open Graph image selection
    • Extended remote image support configuration for development environments

@vercel

vercel Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
storefront Ready Ready Preview, Comment Apr 1, 2026 2:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Product media and variant display logic is refactored to use product-level media with variant-aware gallery highlighting via a new activeIndex prop. Strikethrough pricing logic is adjusted for consistency across components. Open Graph metadata switches from multi-image to single-image approach. Remote image patterns are extended for localhost URLs.

Changes

Cohort / File(s) Summary
Gallery & Variant Indexing
src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx, src/components/products/MediaGallery.tsx
Gallery now derives images from product.media with a memoized variantImageIndex that highlights the first media item matching the selected variant. MediaGallery accepts optional activeIndex prop to control image selection from parent component, with useEffect synchronizing state when variant changes.
Strikethrough Pricing
src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx, src/components/products/ProductCard.tsx
Pricing strikethrough logic refined: when on-sale, prioritizes comparing originalPrice.display_amount against displayPrice, falls back to display_compare_at_amount, with explicit ?? null fallback to ensure consistent nullability.
Metadata & Configuration
src/lib/metadata/product.ts, next.config.ts
Open Graph image selection shifted from iterating all media items to using only the first media item with prioritized fallback chain. Next.js remote patterns extended with two additional localhost entries for /rails/active_storage/** paths.

Sequence Diagram

sequenceDiagram
    participant User as User<br/>(selects variant)
    participant PD as ProductDetails
    participant MI as Memoized<br/>variantImageIndex
    participant MG as MediaGallery
    participant UI as Gallery UI

    User->>PD: Select variant (selectedVariant changes)
    PD->>MI: Compute index in product.media<br/>matching variant_ids
    MI-->>PD: variantImageIndex: number | null
    PD->>MG: Pass activeIndex={variantImageIndex}
    MG->>MG: useEffect watches activeIndex
    alt activeIndex is non-null
        MG->>MG: setSelectedIndex(activeIndex)
        MG->>MG: Clear mainImageErrorUrl
    end
    MG->>UI: Render gallery with highlighted image
    UI-->>User: Display variant's media item highlighted
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through variant dreams,
Where media dances in moonlit beams,
Gallery images now sing as one,
Strikethrough prices cleared in the sun,
And metadata blooms—one image so bright!

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: sale price fixes, variant image switching in the gallery, and OG image optimization. It is concise, specific, and directly reflects the core objectives of the changeset.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pdp-sale-prices-variant-images-og

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/products/MediaGallery.tsx`:
- Around line 24-35: selectedIndex is always initialized to 0 and the useEffect
only updates it when activeIndex != null, which causes the gallery to show an
incorrect image or retain the previous variant image when activeIndex is null;
change the initialization to derive from activeIndex (e.g., useState(activeIndex
?? 0)) and update the effect to setSelectedIndex(activeIndex ?? 0) and reset
mainImageErrorUrl via setMainImageErrorUrl(null) so the gallery correctly
initializes and resets whenever activeIndex changes (refer to selectedIndex,
setSelectedIndex, activeIndex, useEffect, and setMainImageErrorUrl).

In `@src/lib/metadata/product.ts`:
- Around line 38-50: The ogImage object currently hardcodes width: 1200 and
height: 630 while using the /_next/image proxy (variables: firstMedia, rawOgUrl,
ogImage, storeUrl); remove the hardcoded width/height when building the proxy
URL so the metadata only includes url and alt for proxied images (i.e., when
ogImage is created from `${storeUrl}/_next/image?...` leave out width/height).
To provide a true 1200×630 image for social previews, add a dedicated
opengraph-image route (e.g., app/products/[slug]/opengraph-image.tsx) that
exports size = {width:1200,height:630}, contentType = "image/png" and a default
Image component using ImageResponse to render the exact 1200×630 image, then
point metadata to that route when available so metadata can safely include
width/height matching the served image.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4f9bdb3-c22d-4b8e-ba67-3fdd8a4e638e

📥 Commits

Reviewing files that changed from the base of the PR and between 4708e70 and d64d102.

📒 Files selected for processing (5)
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/lib/data/cached.ts
  • src/lib/metadata/product.ts

Comment thread src/components/products/MediaGallery.tsx Outdated
Comment thread src/lib/metadata/product.ts

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (1)
src/components/products/MediaGallery.tsx (1)

30-35: ⚠️ Potential issue | 🟠 Major

The null case is still not handled in the useEffect.

The initialization was fixed, but the useEffect still skips the null case. When a user switches from a variant with assigned media to one without (where activeIndex becomes null), the gallery retains the previous variant's image index instead of resetting to 0.

Proposed fix
   useEffect(() => {
-    if (activeIndex != null) {
-      setSelectedIndex(activeIndex);
-      setMainImageErrorUrl(null);
-    }
+    setSelectedIndex(activeIndex ?? 0);
+    setMainImageErrorUrl(null);
   }, [activeIndex]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/MediaGallery.tsx` around lines 30 - 35, useEffect
currently only handles activeIndex != null, so when activeIndex becomes null the
gallery keeps the previous selected index; update the effect that references
activeIndex (the useEffect containing setSelectedIndex and setMainImageErrorUrl)
to explicitly handle the null case by calling setSelectedIndex(0) and clearing
setMainImageErrorUrl(null) when activeIndex is null, while preserving the
existing branch when activeIndex is a number.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/products/MediaGallery.tsx`:
- Around line 30-35: useEffect currently only handles activeIndex != null, so
when activeIndex becomes null the gallery keeps the previous selected index;
update the effect that references activeIndex (the useEffect containing
setSelectedIndex and setMainImageErrorUrl) to explicitly handle the null case by
calling setSelectedIndex(0) and clearing setMainImageErrorUrl(null) when
activeIndex is null, while preserving the existing branch when activeIndex is a
number.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88c0f91a-8b35-43a7-ba08-afe6478ae8dc

📥 Commits

Reviewing files that changed from the base of the PR and between d64d102 and dd96947.

📒 Files selected for processing (2)
  • src/components/products/MediaGallery.tsx
  • src/lib/metadata/product.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/metadata/product.ts

Cichorek and others added 4 commits April 1, 2026 16:33
- Fix strikethrough price not showing on PDP and ProductCard when
  original_price is null (compare-at price sales)
- Show all product images in gallery and auto-switch to variant's
  image when selecting a variant with assigned media
- Add variants.media to product expand for variant image data
- Use single OG image proxied through Next.js image optimization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Initialize selectedIndex from activeIndex prop instead of always 0
- Use Spree's og_image_url (pre-resized 1200x630) for OG image source
- Only include width/height in metadata when og_image_url is available

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match variant images via media.variant_ids.includes(selectedVariant.id)
instead of expanding variants.media — product media already contains
variant_ids for matching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow next/image to load images from localhost and *.localhost
for local development with Spree backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
next.config.ts (1)

34-43: Remove the duplicated localhost remote pattern.

The entry on Lines 34-38 is identical to the existing one on Lines 19-23, so it does not change the allowlist. Keeping only the new **.localhost rule makes the intent clearer.

♻️ Proposed cleanup
       {
         protocol: "https",
         hostname: "**.spree.sh",
         pathname: "/rails/active_storage/**",
       },
-      {
-        protocol: "http",
-        hostname: "localhost",
-        pathname: "/rails/active_storage/**",
-      },
       {
         protocol: "http",
         hostname: "**.localhost",
         pathname: "/rails/active_storage/**",
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.ts` around lines 34 - 43, There are two identical remotePatterns
entries using protocol: "http", hostname: "localhost", pathname:
"/rails/active_storage/**"; remove the duplicate plain "localhost" object and
keep the intended wildcard entry with hostname: "**.localhost" inside the
remotePatterns array in next.config.ts so the allowlist only contains the
wildcard rule; update the array to contain just the single /**.localhost*/
entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@next.config.ts`:
- Around line 34-43: There are two identical remotePatterns entries using
protocol: "http", hostname: "localhost", pathname: "/rails/active_storage/**";
remove the duplicate plain "localhost" object and keep the intended wildcard
entry with hostname: "**.localhost" inside the remotePatterns array in
next.config.ts so the allowlist only contains the wildcard rule; update the
array to contain just the single /**.localhost*/ entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e8274d8-700b-409e-ada7-51c6e57d32d6

📥 Commits

Reviewing files that changed from the base of the PR and between dd96947 and b8102ff.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • next.config.ts
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/lib/metadata/product.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/products/ProductCard.tsx
  • src/components/products/MediaGallery.tsx
  • src/lib/metadata/product.ts

@damianlegawiec
damianlegawiec merged commit 24649b7 into main Apr 2, 2026
6 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 9, 2026
12 tasks
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