Skip to content

Feat/web product detail#305

Merged
onerandomdevv merged 6 commits into
devfrom
feat/web-product-detail
May 22, 2026
Merged

Feat/web product detail#305
onerandomdevv merged 6 commits into
devfrom
feat/web-product-detail

Conversation

@amoomustakim-hue
Copy link
Copy Markdown
Collaborator

@amoomustakim-hue amoomustakim-hue commented May 21, 2026

What does this PR do?

Builds W-06: Product Detail Page at /p/[code].

This PR adds the public product detail experience for shoppers, including:

  • Server-rendered product detail route
  • Product image gallery
  • Variant-aware image switching
  • In-stock / out-of-stock status
  • Price display from kobo to naira
  • Compare-at price + SAVE badge
  • Store card
  • Expandable description
  • Product details bottom sheet
  • Variant color and size selectors
  • Out-of-stock/disabled variant states
  • Size guide modal
  • Buyer Protection trust badge
  • Sticky Add to Cart / Buy Now action bar

The page works without authentication and does not build cart, checkout, or order creation.

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Database migration included
  • Chore / maintenance

How to test this

  1. Checkout feat/web-product-detail

  2. Run:

    cd apps/web
    pnpm run lint
    npx tsc --noEmit
    pnpm run build

  3. Visit a product route:

    /p/[productCode]

  4. Confirm:

    • Product loads without auth
    • Gallery renders product images
    • Variant color/size chips render where available
    • Gallery switches when variant image mapping exists
    • Out-of-stock/inactive variants are disabled
    • Product Details sheet opens when specs exist
    • Size Guide opens when size guide config exists
    • Description expands/collapses
    • Price renders correctly from kobo
    • Compare-at price and SAVE badge render correctly
    • Store card does not expose private/internal store fields
    • Add to Cart shows “Cart coming soon”
    • Buy Now shows “Checkout coming soon”

Backend contract

Uses:

  • GET /products/:code

Public response fields used include:

  • name
  • description
  • shortDescription
  • retailPriceKobo
  • compareAtPriceKobo
  • status
  • sku
  • hasVariants
  • variants
  • images
  • sizeGuideConfig
  • productDetails
  • store
  • category
  • productCode

Limitations

  • Delivery preview endpoint is not merged yet, so the page shows:
    “Delivery options will appear at checkout.”
  • Cart is not wired in this task. W-11 will handle cart.
  • Checkout/order creation is not wired in this task. W-09a will handle checkout.
  • Rating, review count, and weekly views are omitted because they are not in the current public product response.
  • Stock quantity is not displayed because ProductStockCache is not returned in the public response.

Pre-commit checklist

  • Web lint/type/build commands pass
  • No backend files changed
  • No env files changed
  • No cart page built
  • No checkout page built
  • No order creation built
  • No hardcoded hex values in page/component files
  • No emojis added to UI
  • Public product page works without auth

Summary by CodeRabbit

  • New Features
    • Product detail page with interactive image gallery (prev/next navigation and dot indicators)
    • Product variant selection via color and size chip selectors
    • Expandable product description and store verification badge display
    • Product specifications sheet modal
    • Interactive size guide with clothing/footwear measurement tables and sizing systems
    • Delivery preview section on product page
    • Add to Cart and Buy Now buttons with sticky price display

Review Change Stack

amoomustakim-hue and others added 2 commits May 21, 2026 22:37
…er protection

- Server Component page at /p/[code] fetches from GET /products/:code
- Client component handles gallery switching, variant chips, read-more,
  product specs sheet, size guide modal, and sticky buy/cart bar
- formatKobo() helper added to lib/utils.ts (Naira display from kobo strings)
- DeliveryPreview shows safe placeholder (backend endpoint not yet merged)
- Cart and checkout actions are stubbed with toast; wired in W-11/W-09a

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- formatKobo: add Number.isFinite guard to return '₦0' for non-numeric input
- ProductDetailClient: show 'Price not available' when displayPriceKobo is
  null instead of passing null to formatKobo; guard Buy Now price label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codesandbox
Copy link
Copy Markdown

codesandbox Bot commented May 21, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@onerandomdevv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 24 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb649391-07ce-483d-81a9-c6d59b9fa7dc

📥 Commits

Reviewing files that changed from the base of the PR and between 536c13f and 38716fb.

📒 Files selected for processing (2)
  • apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx
  • apps/web/src/app/(public)/p/[code]/page.tsx
📝 Walkthrough

Walkthrough

This PR adds a complete public product detail page feature, including server-side data fetching from an API, rich client-side UI with product gallery and variant selection, pricing calculations, and interactive purchase flows with supporting size guides and product details sheets.

Changes

Product Detail Page

Layer / File(s) Summary
Server Page Setup & Data Models
apps/web/src/app/(public)/p/[code]/page.tsx
Defines Next.js API route with product data contracts (PublicProduct, ProductVariant, ProductImage, ProductSizeGuideConfig), a server-side fetchProduct() that handles API errors gracefully, and generateMetadata for SEO/OpenGraph; ProductPage calls notFound() for missing or deleted products.
Image Gallery & Variant Selection Components
apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx (lines 1–201)
Helper functions map store verification tiers and select color-scoped gallery images with fallbacks; Gallery component renders main/thumbnail images with navigation; VariantChip component provides reusable color/size selector buttons with disabled state styling.
Product Detail Client - Main Display & Purchase Flow
apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx (lines 205–590)
ProductDetailClient manages color/size selection state, derives active variant and price display (including compare-at and save %), renders product name/description with read-more toggle, store card with verification badge, buyer protection panel, delivery preview, and conditionally opens detail/size guide sheets. Sticky bottom action bar with "Add to Cart" and "Buy Now" buttons validates required variant selections and shows status feedback.
Size Guide, Product Details Sheet & Delivery Components
apps/web/src/components/product/SizeGuide.tsx, apps/web/src/components/product/ProductDetailsSheet.tsx, apps/web/src/components/product/DeliveryPreview.tsx
SizeGuide provides tabbed sections for clothing (women/men/children/unisex) and footwear with embedded size datasets, dynamic measurement tables, and a "How to Measure" guide with footwear size-system switching (EU/UK/US). ProductDetailsSheet renders a bottom sheet listing product specs and SKU. DeliveryPreview is a placeholder UI pending backend delivery estimates endpoint.
Currency Formatting Utility
apps/web/src/lib/utils.ts
formatKobo() converts kobo amounts to Nigerian Naira currency strings using Intl.NumberFormat, with null and non-finite value handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • coded-devs/twizrr#246: Scaffolded a placeholder "Coming Soon" page at the same product detail route; this PR replaces it with real fetching and full UI implementation.
  • coded-devs/twizrr#299: This PR's variant selection, color-scoped image assignments, and size guide UI directly depend on backend product variants, image assignment support, and size guide config added in that PR.

Suggested reviewers

  • onerandomdevv

Poem

🐰 A gallery of colors spins,
Variants dance, the cart now wins,
Size guides measure from head to toe,
Nigerian Naira starts to flow—
The product detail page, at last, takes flight!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title "Feat/web product detail" is partially related to the changeset—it refers to a real part of the change (adding a product detail page to the web app) but is overly broad and vague, lacking specificity about the main deliverable. Improve the title to be more specific and descriptive, such as "Add public product detail page with gallery, variants, and size guide" to better convey the key features being implemented.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive and well-structured, covering objectives, testing steps, backend contract, limitations, and a detailed pre-commit checklist with all items checked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-product-detail

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
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: 4

🧹 Nitpick comments (1)
apps/web/src/app/(public)/p/[code]/page.tsx (1)

9-78: ⚡ Quick win

Move shared product contracts out of the route module.

PublicProduct and related types are imported by UI components from page.tsx, which couples reusable UI to a route file path. Move these interfaces to a shared domain types module (e.g., apps/web/src/types/product.ts) and import them from both page and components.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(public)/p/[code]/page.tsx around lines 9 - 78, The product
contract interfaces (PublicProduct, ProductVariant, ProductImage,
ProductSizeGuideConfig, ProductDetail) are declared in the route module and need
to be relocated to a shared types module so UI components can import them
without depending on the page file; create a new file (e.g.,
apps/web/src/types/product.ts) and move the exported interfaces (PublicProduct,
ProductVariant, ProductImage, ProductSizeGuideConfig, ProductDetail) there,
update page.tsx to import those types from the new module, and update any
components that currently import these types from page.tsx to instead import
from the new shared product types module.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/`(public)/p/[code]/page.tsx:
- Around line 120-122: generateMetadata is leaking deleted product details
because it only checks for product presence; update generateMetadata to apply
the same deleted-product guard used in ProductPage (e.g., check product.status
=== 'deleted' or product.deleted) and return the not-found metadata object
(title: "Product not found — twizrr") when the product is deleted or otherwise
blocked. Locate generateMetadata in the file and mirror the ProductPage status
check so deleted products produce the not-found metadata.

In `@apps/web/src/app/`(public)/p/[code]/ProductDetailClient.tsx:
- Around line 352-354: The product heading in ProductDetailClient is using the
wrong font class ("font-cabinet"); change it to use the Syne heading font
(weight 700) as required by our guidelines: replace the "font-cabinet" class on
the <h1> for product.name with the Syne font utility or the Next/font-generated
class (e.g., syne.className or "font-syne") and ensure Syne is
imported/initialized via next/font/google with weight 700 in the component or a
shared layout so headings render with Syne (700) consistently.
- Around line 294-305: validateVariantAndProceed currently only ensures
color/size are chosen but doesn't verify the selected combination maps to an
active variant; update this function to look up the matching variant from
product.variants using selectedColor and/or selectedSize and ensure that
variant.exists && variant.active before returning true — if no active match,
call toast("Please select a valid, available variant", { variant: "warning" })
and return false. Apply the same existence+active check to the other purchase
flow block referenced at 307-317 (the add-to-cart / buy-now handler) so both
paths block when no valid active variant is selected.

In `@apps/web/src/components/product/DeliveryPreview.tsx`:
- Around line 21-34: DeliveryPreview currently renders only a placeholder;
implement a fetch flow in the DeliveryPreview component that GETs
/delivery/estimates with productId, shopperState, and shopperCity, using the
existing productId prop (productId: _productId) as the productId parameter;
inside the component add local state (e.g., estimates, loading, error), trigger
the request in useEffect (include cleanup/abort on unmount), build the query
string like
/delivery/estimates?productId=${_productId}&shopperState=${state}&shopperCity=${city},
handle JSON response and errors, and render the returned estimates instead of
the placeholder when available (keep the Truck icon and the placeholder text as
the fallback UI when loading or on error/no-data).

---

Nitpick comments:
In `@apps/web/src/app/`(public)/p/[code]/page.tsx:
- Around line 9-78: The product contract interfaces (PublicProduct,
ProductVariant, ProductImage, ProductSizeGuideConfig, ProductDetail) are
declared in the route module and need to be relocated to a shared types module
so UI components can import them without depending on the page file; create a
new file (e.g., apps/web/src/types/product.ts) and move the exported interfaces
(PublicProduct, ProductVariant, ProductImage, ProductSizeGuideConfig,
ProductDetail) there, update page.tsx to import those types from the new module,
and update any components that currently import these types from page.tsx to
instead import from the new shared product types module.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75ac33a2-df5c-4bd4-bdab-ef49de283672

📥 Commits

Reviewing files that changed from the base of the PR and between 4306f13 and 536c13f.

📒 Files selected for processing (6)
  • apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx
  • apps/web/src/app/(public)/p/[code]/page.tsx
  • apps/web/src/components/product/DeliveryPreview.tsx
  • apps/web/src/components/product/ProductDetailsSheet.tsx
  • apps/web/src/components/product/SizeGuide.tsx
  • apps/web/src/lib/utils.ts

Comment thread apps/web/src/app/(public)/p/[code]/page.tsx Outdated
Comment thread apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx
Comment on lines +352 to +354
<h1 className="text-2xl font-bold font-cabinet text-[var(--foreground)] leading-tight">
{product.name}
</h1>
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 | ⚡ Quick win

Use Syne for the product heading.

This heading uses font-cabinet; heading typography should use Syne.

As per coding guidelines, "Use Syne font (weight 700) for all headings via next/font/google".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(public)/p/[code]/ProductDetailClient.tsx around lines 352
- 354, The product heading in ProductDetailClient is using the wrong font class
("font-cabinet"); change it to use the Syne heading font (weight 700) as
required by our guidelines: replace the "font-cabinet" class on the <h1> for
product.name with the Syne font utility or the Next/font-generated class (e.g.,
syne.className or "font-syne") and ensure Syne is imported/initialized via
next/font/google with weight 700 in the component or a shared layout so headings
render with Syne (700) consistently.

Comment on lines +21 to +34
export function DeliveryPreview({
productId: _productId,
}: DeliveryPreviewProps) {
return (
<div className="flex items-center gap-3 rounded-xl border border-[var(--border)] bg-[var(--card)] p-3">
<Truck
className="h-5 w-5 shrink-0 text-[var(--muted-foreground)]"
aria-hidden="true"
/>
<p className="text-sm font-cabinet text-[var(--muted-foreground)]">
Delivery options will appear at checkout.
</p>
</div>
);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Implement delivery estimates fetch flow instead of placeholder-only UI.

This currently never attempts to load delivery options, so users cannot see estimates on PDP. Keep the placeholder as fallback, but wire the endpoint fetch path in this component.

As per coding guidelines, "Show product delivery estimates by fetching GET /delivery/estimates with productId, shopperState, and shopperCity parameters".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/product/DeliveryPreview.tsx` around lines 21 - 34,
DeliveryPreview currently renders only a placeholder; implement a fetch flow in
the DeliveryPreview component that GETs /delivery/estimates with productId,
shopperState, and shopperCity, using the existing productId prop (productId:
_productId) as the productId parameter; inside the component add local state
(e.g., estimates, loading, error), trigger the request in useEffect (include
cleanup/abort on unmount), build the query string like
/delivery/estimates?productId=${_productId}&shopperState=${state}&shopperCity=${city},
handle JSON response and errors, and render the returned estimates instead of
the placeholder when available (keep the Truck icon and the placeholder text as
the fallback UI when loading or on error/no-data).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@claude attend to this

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

- generateMetadata now mirrors the ProductPage guard and returns the
  not-found metadata when product.status === "DELETED" so deleted
  products do not leak names, descriptions, or OG images.
- validateVariantAndProceed also asserts the selected color/size combo
  resolves to an existing active variant, blocking add-to-cart and
  buy-now when the picked combination has no active match.
@onerandomdevv onerandomdevv merged commit 6cb3ca5 into dev May 22, 2026
8 checks passed
@onerandomdevv onerandomdevv deleted the feat/web-product-detail branch May 22, 2026 04:25
@coderabbitai coderabbitai Bot mentioned this pull request May 22, 2026
21 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