Feat/web product detail#305
Conversation
…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>
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis 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. ChangesProduct Detail Page
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
apps/web/src/app/(public)/p/[code]/page.tsx (1)
9-78: ⚡ Quick winMove shared product contracts out of the route module.
PublicProductand related types are imported by UI components frompage.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
📒 Files selected for processing (6)
apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsxapps/web/src/app/(public)/p/[code]/page.tsxapps/web/src/components/product/DeliveryPreview.tsxapps/web/src/components/product/ProductDetailsSheet.tsxapps/web/src/components/product/SizeGuide.tsxapps/web/src/lib/utils.ts
| <h1 className="text-2xl font-bold font-cabinet text-[var(--foreground)] leading-tight"> | ||
| {product.name} | ||
| </h1> |
There was a problem hiding this comment.
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.
| 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> | ||
| ); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
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:
The page works without authentication and does not build cart, checkout, or order creation.
Type of change
How to test this
Checkout
feat/web-product-detailRun:
cd apps/web
pnpm run lint
npx tsc --noEmit
pnpm run build
Visit a product route:
/p/[productCode]
Confirm:
Backend contract
Uses:
GET /products/:codePublic response fields used include:
namedescriptionshortDescriptionretailPriceKobocompareAtPriceKobostatusskuhasVariantsvariantsimagessizeGuideConfigproductDetailsstorecategoryproductCodeLimitations
“Delivery options will appear at checkout.”
ProductStockCacheis not returned in the public response.Pre-commit checklist
Summary by CodeRabbit