Migrate checkout, shipping, and review forms to shadcn primitives and product Data update - #5
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 53 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR removes shipping fees across checkout, cart, and content pages, migrates form controls to shared UI components, derives product sizes from category data with Sanity validation, and updates API, accessibility, layout, and documentation files. ChangesFree shipping and size catalog changes
Shared UI component migration
Accessibility, API cleanup, and documentation
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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.
Pull request overview
This PR modernizes the storefront UI by migrating several form surfaces to shadcn/base-ui primitives, while also aligning product sizing/shipping policy content and adding launch-readiness audit docs.
Changes:
- Added shadcn/base-ui primitives (Button/Input/Select/etc.) and updated global Tailwind styling imports.
- Updated product sizing rules (attar vs perfume) across Sanity schema validation and fallback product data.
- Made shipping free across checkout/cart UI and updated related policy/FAQ content; added
noindexmeta for cart/checkout/login via routehead.tsx.
Reviewed changes
Copilot reviewed 40 out of 42 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| sanity/schemas/product.ts | Adds async validation to restrict allowed sizes based on product category reference. |
| package.json | Adds base-ui/shadcn-related dependencies and Tailwind helper libs. |
| lib/utils.ts | Replaces prior utilities with shadcn-style cn() helper (currently breaks missing exports). |
| lib/mail/index.ts | Normalizes contact email subject before sending. |
| lib/mail/config.ts | Updates ADMIN_EMAIL env fallback/trim behavior. |
| lib/data.ts | Deduplicates size arrays and aligns attar/perfume sizing. |
| eslint.config.mjs | Minor config comment change (currently duplicated). |
| docs/pre-launch-implementation-plan.md | Adds pre-launch implementation plan doc. |
| docs/pre-launch-audit.md | Adds pre-launch audit doc. |
| components/ui/Toast.tsx | Minor toast sizing + a11y label for dismiss button. |
| components/ui/textarea.tsx | Adds shadcn-style Textarea primitive. |
| components/ui/select.tsx | Adds base-ui Select wrapper styled via Tailwind/shadcn patterns. |
| components/ui/label.tsx | Adds shadcn-style Label primitive. |
| components/ui/input.tsx | Adds base-ui Input wrapper. |
| components/ui/checkbox.tsx | Adds base-ui Checkbox wrapper. |
| components/ui/Button.tsx | Reimplements Button using base-ui + CVA variants. |
| components/shop/ProductReviews.tsx | Migrates review form controls to new primitives and adds ARIA labels. |
| components/shop/ProductGrid.tsx | Removes unused index prop from ProductCard usage. |
| components/shop/ProductCard.tsx | Updates sizing selection logic and layout; (currently removes cart add path). |
| components/profile/ShippingAddresses.tsx | Migrates shipping address form to new primitives and base-ui Select/Checkbox. |
| components/product/ProductInfo.tsx | Filters sizes via new helper and adds “free delivery” note. |
| components/product/ProductGallery.tsx | Removes aria-pressed from gallery thumb buttons (a11y regression). |
| components/layout/Navbar.tsx | Updates logo sizing props to address next/image warnings. |
| components/layout/ClientLayout.tsx | Adds skip link and anchors main content for accessibility. |
| components/home/FeaturedProducts.tsx | Removes unused ProductCard index prop. |
| components/cart/CartSummary.tsx | Makes shipping always free and simplifies display. |
| components.json | Adds shadcn config file for generator/aliases. |
| app/track-order/[id]/page.tsx | Adds ARIA labels to review modal controls. |
| app/size-guide/page.tsx | Rebuilds size guide to read sizes/prices from Sanity (fallback to local data). |
| app/shop/ShopClient.tsx | Removes aria-expanded from mobile filter toggle (a11y regression). |
| app/shipping-policy/page.tsx | Updates shipping policy copy to reflect free shipping + express option. |
| app/login/head.tsx | Adds noindex, nofollow meta for login route. |
| app/layout.tsx | Adds Geist font + uses cn() to build html className. |
| app/globals.css | Imports tw-animate + shadcn CSS and adds shadcn theme token layer. |
| app/faq/page.tsx | Updates FAQ to reflect new sizes + free shipping policy. |
| app/checkout/page.tsx | Migrates address controls to new primitives and makes shipping free. |
| app/checkout/head.tsx | Adds noindex, nofollow meta for checkout route. |
| app/cart/head.tsx | Adds noindex, nofollow meta for cart route. |
| app/api/contact/route.ts | Normalizes/sanitizes inputs and improves error handling for email delivery. |
| app/api/auth/me/route.ts | Removes unused request param/import. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { clsx, type ClassValue } from "clsx" | ||
| import { twMerge } from "tailwind-merge" | ||
|
|
||
| url = url.includes("http") ? url : `https://${url}`; | ||
| url = url.endsWith("/") ? url.slice(0, -1) : url; | ||
| return url; | ||
| export function cn(...inputs: ClassValue[]) { | ||
| return twMerge(clsx(inputs)) | ||
| } |
| variants: { | ||
| variant: { | ||
| default: "bg-primary text-primary-foreground hover:bg-primary/80", | ||
| outline: | ||
| "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", | ||
| secondary: | ||
| "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", | ||
| ghost: | ||
| "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", | ||
| destructive: | ||
| "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", | ||
| link: "text-primary underline-offset-4 hover:underline", | ||
| }, |
| if (href) { | ||
| return ( | ||
| <Link href={href} className={classes} id={id} target={target} rel={rel}> | ||
| <Link href={href} className={classes} target={target} rel={rel}> | ||
| {children} | ||
| </Link> | ||
| ); | ||
| ) |
| @@ -101,7 +106,6 @@ export function ProductInfo({ product }: { product: Product }) { | |||
| : "border border-border text-text-muted hover:border-gold hover:text-gold" | |||
| }`} | |||
| id={`size-${size.value}`} | |||
| aria-pressed={selectedSize.value === size.value} | |||
| > | |||
| <button | ||
| key={i} | ||
| onClick={() => setActiveIndex(i)} | ||
| className={`relative w-20 h-20 overflow-hidden transition-all duration-300 ${ | ||
| i === activeIndex | ||
| ? "border-2 border-gold" | ||
| : "border border-border opacity-60 hover:opacity-100" | ||
| }`} | ||
| id={`gallery-thumb-${i}`} | ||
| aria-label={`Select image ${i + 1} for ${name}`} | ||
| aria-pressed={i === activeIndex} | ||
| > |
| export const ADMIN_EMAIL = ( | ||
| process.env.ADMIN_EMAIL || process.env.WEBSITE_EMAIL || "" | ||
| ).trim(); |
| "sanity": "^5.21.0", | ||
| "tailwindcss": "^4" | ||
| "shadcn": "^4.13.0", | ||
| "tailwind-merge": "^3.6.0", | ||
| "tailwindcss": "^4", | ||
| "tw-animate-css": "^1.4.0" |
| @import "tailwindcss"; | ||
| @import "tw-animate-css"; | ||
| @import "shadcn/tailwind.css"; | ||
|
|
||
| @custom-variant dark (&:is(.dark *)); |
| // Override default ignores of eslint-config-next. | ||
| // Override default ignores of eslint-config-next. |
| const { /*addItem,*/ removeItem, updateQuantity, items } = useCart(); | ||
|
|
||
| const hasSizes = product.sizes && product.sizes.length > 0; | ||
| const initialSize = hasSizes ? product.sizes![0].value : ""; | ||
| const availableSizes = getAllowedProductSizes(product.category, product.sizes ?? []); | ||
| const hasSizes = availableSizes && availableSizes.length > 0; | ||
| const initialSize = hasSizes ? availableSizes[0].value : ""; |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
app/api/contact/route.ts (1)
24-58: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPersistence failures are silently swallowed and the user is told the submission succeeded.
If
prisma.contactSubmission.createfails, the error is only logged (Line 34) and execution continues; as long as the email send succeeds, the handler returns{ success: true }at Line 55-58. The submission is permanently lost with no durable record, no alert, and no indication to the user or admin that the DB write failed.Consider at minimum: alerting/monitoring on this log line, or reflecting a partial-failure state to the caller so the loss isn't completely silent.
🤖 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 `@app/api/contact/route.ts` around lines 24 - 58, The contact submission handler currently swallows failures from prisma.contactSubmission.create and still returns success if sendContactNotificationEmail works, so the user is misled and the write loss is silent. Update the route in contactSubmission persistence and the email flow so a database write failure is not treated as a successful request: either propagate the dbError and return an error/partial-failure response, or explicitly surface the degraded state to the caller, and keep the logging/monitoring tied to the create call and the final NextResponse.json path.app/checkout/page.tsx (1)
463-583: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFixed
h-8height onInput/SelectTriggerconflicts with the floating-labelpt-6 pb-2padding, causing content overflow/clipping.All migrated fields here (pincode, city, name, email, phone, address) apply
pt-6 pb-2(32px vertical padding) on top of the sharedInputcomponent's baseh-8(32px fixed height, percomponents/ui/input.tsx), leaving no room for line-height/text content within the box. The same applies to thecheckout-stateSelectTrigger, whose base classes includedata-[size=default]:h-8. Since neither customclassNameincludes a height override,h-8survives thecn()/tailwind-merge pass and the floating-label design (label shrinks to top, value renders below it) can't actually fit inside a 32px box — text will be visually clipped or overflow the box.This regression is introduced by wrapping these fields with the shared
Input/SelectTriggerprimitives, which weren't a concern before migration since the previous native<input>/<select>had no forced height.🐛 Suggested fix: override height for floating-label fields
- <Input - id="checkout-pincode" - ... - className={`peer w-full bg-white/5 border ${pincodeError ? "border-red-500/50 focus:border-red-500" : "border-white/10 focus:border-gold"} rounded-xl px-4 pt-6 pb-2 text-text-light placeholder-transparent focus:outline-none transition-all`} + <Input + id="checkout-pincode" + ... + className={`peer h-14 w-full bg-white/5 border ${pincodeError ? "border-red-500/50 focus:border-red-500" : "border-white/10 focus:border-gold"} rounded-xl px-4 pt-6 pb-2 text-text-light placeholder-transparent focus:outline-none transition-all`}Apply the same
h-14(orh-auto) override to the city, name, email, phone, addressInputs and thecheckout-stateSelectTrigger.🤖 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 `@app/checkout/page.tsx` around lines 463 - 583, The floating-label checkout fields are still inheriting the shared fixed height from Input and SelectTrigger, which conflicts with the pt-6 pb-2 layout and causes clipped or overflowing text. Update the checkout form fields in the checkout page so the pincode, city, name, email, phone, address Inputs and the checkout-state SelectTrigger explicitly override the default h-8 sizing with a taller height such as h-14 or h-auto, while keeping the existing floating-label padding and label positioning intact.components/shop/ProductCard.tsx (1)
102-113: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCart-count badge may clip for two-digit quantities.
The badge is a fixed
w-6 h-6circle; addingmin-w-4doesn't help since it's smaller than the existing fixed width. OncecartQtyreaches double digits, the number can overflow/clip.🎨 Suggested fix to allow the badge to grow
- className="absolute top-3 right-12 w-6 h-6 bg-gold text-primary text-[10px] font-bold rounded-full flex items-center justify-center shadow-lg min-w-4" + className="absolute top-3 right-12 min-w-6 h-6 px-1 bg-gold text-primary text-[10px] font-bold rounded-full flex items-center justify-center shadow-lg"🤖 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 `@components/shop/ProductCard.tsx` around lines 102 - 113, The cart-count badge in ProductCard’s AnimatePresence/motion.div is fixed at a circular size, so double-digit cartQty values can clip. Update the badge styling to allow the element to expand with the content by replacing the fixed width/height behavior with size that can grow naturally while keeping the centered badge layout and animation intact.components/product/ProductInfo.tsx (2)
99-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
aria-pressedremoved from toggle-style size buttons.The size buttons behave like a toggle group (one selected at a time, visually indicated by classes), but the
aria-pressedstate that previously exposed this to assistive tech was removed with no replacement (e.g.,role="radiogroup"/aria-checked).♿ Suggested restore
<button key={size.value} onClick={() => setSelectedSize(size)} + aria-pressed={selectedSize.value === size.value} className={`px-6 py-3 text-xs tracking-wider uppercase transition-all duration-300 ${🤖 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 `@components/product/ProductInfo.tsx` around lines 99 - 109, The size selector buttons in ProductInfo are acting like a single-select toggle group, but their accessible selected state was removed. Update the button rendered in the availableSizes.map block so assistive tech can tell which size is selected by restoring an appropriate pressed/checked state on the size button or by converting the group to a proper radiogroup/radio pattern. Use the existing selectedSize state and size.value comparison in ProductInfo to keep the accessibility state in sync with the visual selection.
24-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRestore the
react-hooks/set-state-in-effectsuppression.eslint.config.mjsuses the Next.js core-web-vitals preset, andcomponents/shop/ProductCard.tsxstill needs the samesetMounted(true)exception.🤖 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 `@components/product/ProductInfo.tsx` around lines 24 - 27, The ProductInfo component’s useEffect still triggers the react-hooks/set-state-in-effect rule, so restore the same suppression used in ProductCard for the setMounted(true) call. Update the ProductInfo useEffect block to match the existing exception pattern in ProductCard, keeping the mounted state initialization while preventing the lint error under the Next.js core-web-vitals config.
🧹 Nitpick comments (11)
docs/pre-launch-implementation-plan.md (1)
22-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate the noindex task to the actual route files.
This plan still points future work at
page.tsxmetadata exports, but the branch summary shows the noindex change is being implemented via routehead.tsxfiles. If any of these routes are client components,metadataexports won't be valid, so this guidance will send the next implementer to the wrong place. Please verify the server/client boundary and rewrite the file list accordingly.Also applies to: 57-63
🤖 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 `@docs/pre-launch-implementation-plan.md` around lines 22 - 27, Update the SEO-002 plan to point at the actual route-level implementation used in this branch instead of assuming metadata exports in page components. Verify the server/client boundary for the `/checkout`, `/cart`, and `/login` routes, and rewrite the Files/Approach guidance to reference the correct `head.tsx` or other server-side route files rather than `app/*/page.tsx` with `metadata`. Keep the task aligned with the route files currently used for the noindex change so the next implementer lands in the right place.docs/pre-launch-audit.md (1)
5-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSurface the payment blocker in the summary.
The executive summary says there are no critical blockers, but the ecommerce section later marks payment/checkout verification as critical. That mismatch buries the highest-risk item for skimmers. Reword the summary to call out the payment path explicitly.
Also applies to: 177-185
🤖 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 `@docs/pre-launch-audit.md` around lines 5 - 8, The executive summary in the pre-launch audit is downplaying a critical risk because it says there are no immediate blockers while the ecommerce section flags payment/checkout verification as critical. Update the summary text to explicitly call out the payment path or checkout verification as the highest-risk item, keeping the wording consistent with the later ecommerce assessment in the same document.components/shop/ProductReviews.tsx (3)
48-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnclear benefit from
queueMicrotaskwrapper.
fetchReviewsis already async and invoked insideuseEffect; wrapping the call inqueueMicrotaskjust defers scheduling by one microtask tick without changing behavior meaningfully (state updates still happen asynchronously either way). This same pattern appears in the checkout and shipping-address forms in this cohort — if it's addressing a specific issue (e.g., Strict Mode double-invoke, hydration timing), a comment explaining the rationale would help future maintainers; otherwise this adds indirection with no observable benefit.♻️ Simplify if no specific issue is being worked around
useEffect(() => { - queueMicrotask(() => { - fetchReviews(); - }); + fetchReviews(); }, [fetchReviews]);🤖 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 `@components/shop/ProductReviews.tsx` around lines 48 - 52, The useEffect in ProductReviews wraps fetchReviews in queueMicrotask without a clear need, adding indirection for no observable benefit. Remove the queueMicrotask wrapper and call fetchReviews directly from the effect, unless this is intentionally working around a specific issue; if so, keep the wrapper but add a brief rationale comment in ProductReviews and apply the same clarification to the matching checkout and shipping-address patterns.
228-270: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDuplicate
aria-labelacross multiple remove-image buttons.Every remove button in the images loop shares the same
aria-label="Remove uploaded image"(Line 239). With multiple images, screen reader users can't distinguish which image a given "Remove uploaded image" button will delete.♿ Proposed fix to disambiguate labels
<Button type="button" onClick={() => removeImage(index)} - aria-label="Remove uploaded image" + aria-label={`Remove uploaded image ${index + 1}`} variant="ghost" size="icon-xs" className="absolute top-1 right-1 bg-black/50 hover:bg-red-500" >🤖 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 `@components/shop/ProductReviews.tsx` around lines 228 - 270, The remove-image buttons in ProductReviews are accessible but not distinguishable because every button in the images loop uses the same aria-label. Update the image preview controls in the images.map block so each remove button includes a unique label that identifies the specific image it will delete, using the loop data (for example the index or a filename/description if available) while keeping the removeImage handler behavior unchanged.
192-213: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd
aria-pressedto the rating buttons Addaria-pressed={star <= rating}so the selected rating is exposed to assistive tech instead of only via color/fill.🤖 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 `@components/shop/ProductReviews.tsx` around lines 192 - 213, The rating controls in ProductReviews use icon buttons but do not expose the selected state to assistive tech. Update the Button inside the star map in ProductReviews so each rating button includes aria-pressed based on whether that star is selected, using the existing star/rating values to keep the pressed state synchronized with the current rating.lib/mail/index.ts (1)
39-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate default-subject logic.
The "General Inquiry" fallback here duplicates the same normalization already performed by the caller (
normalizedSubjectinapp/api/contact/route.ts). Not a bug since it's idempotent, but consider consolidating to a single source of truth if other callers ofsendContactNotificationEmaildon't pre-normalize.🤖 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 `@lib/mail/index.ts` around lines 39 - 44, The subject fallback logic in sendContactNotificationEmail duplicates normalization already done by the caller, so consolidate to a single source of truth. Update the sendContactNotificationEmail flow in lib/mail/index.ts to trust the normalized subject passed in (or centralize the fallback in a shared helper used by both app/api/contact/route.ts and this mail helper) so the "General Inquiry" default is handled consistently across all callers.lib/data.ts (1)
3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueShared array references across products.
ATTAR_SIZES/PERFUME_SIZESare single array instances now reused by reference across many product entries (Lines 35-291). No current code mutatesproduct.sizes, but any future feature that mutates a size array (sort, push, price override, etc.) would silently affect every product sharing the reference.♻️ Defensive fix: clone per product
- sizes: ATTAR_SIZES, + sizes: [...ATTAR_SIZES],🤖 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 `@lib/data.ts` around lines 3 - 9, The shared ATTAR_SIZES and PERFUME_SIZES arrays are being reused by reference across many products, so a mutation in one product could affect others. Update the product construction in the data module so each product receives its own sizes array copy instead of the shared constants, using the existing ATTAR_SIZES and PERFUME_SIZES definitions as the source but cloning them wherever they are assigned to product.sizes.app/faq/page.tsx (1)
14-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded size list may drift from the catalog-driven source of truth.
This FAQ answer hardcodes "6ml and 12ml" for attars and "30ml" for perfumes. This PR's other layers introduce category-based, Sanity-driven size validation (
getAllowedProductSizes, shared size constants inlib/data.ts) and a catalog-driven size-guide page. If the canonical sizes change in Sanity/lib/data.tsin the future, this static FAQ text won't update automatically and could become inaccurate.🤖 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 `@app/faq/page.tsx` around lines 14 - 17, The FAQ entry in the page component is hardcoding attar and perfume sizes, which can drift from the catalog-driven source of truth. Update the answer to derive sizes from the shared size constants or category-based helpers used elsewhere, such as getAllowedProductSizes and the values in lib/data.ts, so the FAQ stays aligned with Sanity-driven size rules. Keep the FAQ text consistent with the same canonical size source used by the size-guide page and product validation.components/profile/ShippingAddresses.tsx (1)
17-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistent casing across new
ui/primitive imports.
Buttonis imported from@/components/ui/Button(capitalized) whileCheckbox,Input,Label,Selectcome from lowercase filenames. Functionally fine on case-sensitive filesystems since it matches the actual file names, but the mixed convention is worth aligning for consistency across the new design-system 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 `@components/profile/ShippingAddresses.tsx` around lines 17 - 28, The new ui primitive imports in ShippingAddresses are using mixed filename casing, which should be aligned for consistency. Update the imports in the ShippingAddresses component so the design-system primitives all follow the same casing convention as their actual file names, and keep the import paths for Button, Checkbox, Input, Label, and Select consistent with the rest of the ui/ primitives.app/globals.css (1)
2-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStylelint
@custom-variant/@themewarnings are false positives.Both at-rules are valid Tailwind CSS v4 syntax; the Stylelint config's
scss/at-rule-no-unknownrule doesn't recognize them. Consider adding the official Tailwind Stylelint plugin/config or extendingignoreAtRulesto silence this noise in CI.Also applies to: 303-426
🤖 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 `@app/globals.css` around lines 2 - 5, The Stylelint warning is a false positive because the Tailwind v4 at-rules used in globals.css are valid. Update the Stylelint configuration to recognize Tailwind-specific at-rules by either adding the official Tailwind Stylelint plugin/config or extending scss/at-rule-no-unknown ignoreAtRules to include `@custom-variant` and `@theme`. Make sure the fix applies globally so other Tailwind declarations in the stylesheet are not flagged.Source: Linters/SAST tools
package.json (1)
33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider moving
shadcntodevDependencies.
shadcnis a CLI/scaffolding tool consumed vianpx shadcn ..., not imported by application code. Keeping it independenciesmeans it gets installed in production installs (e.g.,npm ci --omit=dev), adding unnecessary install time/surface.♻️ Proposed fix
"dependencies": { "`@base-ui/react`": "^1.6.0", ... - "shadcn": "^4.13.0", "tailwind-merge": "^3.6.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0" }, + "devDependencies": { + "shadcn": "^4.13.0" + }🤖 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 `@package.json` at line 33, Move the shadcn package from dependencies to devDependencies in package.json, since it is only used as a CLI via npx shadcn and not imported by runtime code. Keep the existing version specifier unchanged, and update the manifest so production installs do not include this tooling dependency.
🤖 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 `@app/cart/head.tsx`:
- Around line 1-7: The robots meta in Head is not being rendered in this App
Router setup, so move the noindex/nofollow directive into the Metadata API for
the affected routes. Remove the Head-based meta from the cart, checkout, and
login segments, and add robots: { index: false, follow: false } in the
appropriate route segment exports instead, using the existing layout/page entry
points like cart/layout, login/layout, and checkout/page or checkout/layout.
In `@app/size-guide/page.tsx`:
- Around line 28-32: Update the page metadata in the size guide component so it
matches the new “Attar and Perfume” content. In the exported metadata object for
the page, change the title from the attar-only wording and revise the
description to mention both attar and perfume, keeping it aligned with the page
heading and dedicated perfume section.
- Around line 123-166: The “From ₹X” value in buildGuideCards is taken from the
first product in a category instead of the lowest available price, so update the
pricing source to use the true minimum across categoryProducts before passing it
into buildCard. Also make sure the size selection logic in buildGuideCards still
deduplicates by size value, but the displayed price is derived from the
category’s minimum product price rather than categoryProducts[0]?.price.
- Around line 78-121: Handle unexpected attar sizes in the attar branch of the
size guide helper so they do not fall through to the generic spray copy. In the
size mapping logic in app/size-guide/page.tsx, update the attar-specific
handling around the category === "attar" switch to return an attar fallback
object for unknown size.value cases instead of the shared default; keep the
fallback copy, title, applications, duration, and description aligned with attar
products, while preserving the existing 6ml and 12ml entries in the same
function.
In `@components/product/ProductInfo.tsx`:
- Line 6: The import in ProductInfo.tsx is unresolved because
getAllowedProductSizes is not exported from lib/utils.ts. Update the utils
module to export getAllowedProductSizes alongside the existing helper exports,
and make sure any shared re-exports used by ProductInfo and ProductCard remain
consistent so both imports resolve correctly.
In `@components/profile/ShippingAddresses.tsx`:
- Around line 92-102: The ShippingAddresses useEffect currently wraps
fetchAddresses and setLoading(false) in queueMicrotask, which adds an
unnecessary extra tick. Remove the queueMicrotask wrapper inside
ShippingAddresses and let the effect call fetchAddresses or setLoading directly,
and if there is a concern about updates after unmount, handle that with cleanup
or abort logic in the effect itself.
In `@components/ui/Button.tsx`:
- Around line 65-71: The Button component’s href branch drops forwarded props,
so link-styled buttons lose attributes like id, event handlers, and aria-* from
the Button API. Update the href path in Button to pass through the remaining
props to Link while still setting className, target, and rel, matching the
non-href branch behavior.
In `@lib/utils.ts`:
- Around line 1-6: Restore the missing utility export in lib/utils.ts by
reintroducing formatPrice alongside cn, since CartSummary, CartItem,
ProductInfo, and ProductCard still import it from `@/lib/utils`. Update the utils
module so formatPrice is exported again with the same name/signature expected by
those components, and keep the existing cn helper intact.
In `@sanity/schemas/product.ts`:
- Around line 106-132: The size validator in the product schema currently fails
open in the catRef/category lookup path, so invalid sizes can slip through
whenever the fetch fails or the category is unrecognized. Update the validation
logic around the anonymous validator in product.ts to treat missing catRef or
lookup errors as a hard validation failure (or an explicit flagged state), and
only return true when the category is known and matches the allowed size rules
for attar/perfume. Also add logging or a clear error path in the catch block so
bypasses are visible for auditing.
---
Outside diff comments:
In `@app/api/contact/route.ts`:
- Around line 24-58: The contact submission handler currently swallows failures
from prisma.contactSubmission.create and still returns success if
sendContactNotificationEmail works, so the user is misled and the write loss is
silent. Update the route in contactSubmission persistence and the email flow so
a database write failure is not treated as a successful request: either
propagate the dbError and return an error/partial-failure response, or
explicitly surface the degraded state to the caller, and keep the
logging/monitoring tied to the create call and the final NextResponse.json path.
In `@app/checkout/page.tsx`:
- Around line 463-583: The floating-label checkout fields are still inheriting
the shared fixed height from Input and SelectTrigger, which conflicts with the
pt-6 pb-2 layout and causes clipped or overflowing text. Update the checkout
form fields in the checkout page so the pincode, city, name, email, phone,
address Inputs and the checkout-state SelectTrigger explicitly override the
default h-8 sizing with a taller height such as h-14 or h-auto, while keeping
the existing floating-label padding and label positioning intact.
In `@components/product/ProductInfo.tsx`:
- Around line 99-109: The size selector buttons in ProductInfo are acting like a
single-select toggle group, but their accessible selected state was removed.
Update the button rendered in the availableSizes.map block so assistive tech can
tell which size is selected by restoring an appropriate pressed/checked state on
the size button or by converting the group to a proper radiogroup/radio pattern.
Use the existing selectedSize state and size.value comparison in ProductInfo to
keep the accessibility state in sync with the visual selection.
- Around line 24-27: The ProductInfo component’s useEffect still triggers the
react-hooks/set-state-in-effect rule, so restore the same suppression used in
ProductCard for the setMounted(true) call. Update the ProductInfo useEffect
block to match the existing exception pattern in ProductCard, keeping the
mounted state initialization while preventing the lint error under the Next.js
core-web-vitals config.
In `@components/shop/ProductCard.tsx`:
- Around line 102-113: The cart-count badge in ProductCard’s
AnimatePresence/motion.div is fixed at a circular size, so double-digit cartQty
values can clip. Update the badge styling to allow the element to expand with
the content by replacing the fixed width/height behavior with size that can grow
naturally while keeping the centered badge layout and animation intact.
---
Nitpick comments:
In `@app/faq/page.tsx`:
- Around line 14-17: The FAQ entry in the page component is hardcoding attar and
perfume sizes, which can drift from the catalog-driven source of truth. Update
the answer to derive sizes from the shared size constants or category-based
helpers used elsewhere, such as getAllowedProductSizes and the values in
lib/data.ts, so the FAQ stays aligned with Sanity-driven size rules. Keep the
FAQ text consistent with the same canonical size source used by the size-guide
page and product validation.
In `@app/globals.css`:
- Around line 2-5: The Stylelint warning is a false positive because the
Tailwind v4 at-rules used in globals.css are valid. Update the Stylelint
configuration to recognize Tailwind-specific at-rules by either adding the
official Tailwind Stylelint plugin/config or extending scss/at-rule-no-unknown
ignoreAtRules to include `@custom-variant` and `@theme`. Make sure the fix applies
globally so other Tailwind declarations in the stylesheet are not flagged.
In `@components/profile/ShippingAddresses.tsx`:
- Around line 17-28: The new ui primitive imports in ShippingAddresses are using
mixed filename casing, which should be aligned for consistency. Update the
imports in the ShippingAddresses component so the design-system primitives all
follow the same casing convention as their actual file names, and keep the
import paths for Button, Checkbox, Input, Label, and Select consistent with the
rest of the ui/ primitives.
In `@components/shop/ProductReviews.tsx`:
- Around line 48-52: The useEffect in ProductReviews wraps fetchReviews in
queueMicrotask without a clear need, adding indirection for no observable
benefit. Remove the queueMicrotask wrapper and call fetchReviews directly from
the effect, unless this is intentionally working around a specific issue; if so,
keep the wrapper but add a brief rationale comment in ProductReviews and apply
the same clarification to the matching checkout and shipping-address patterns.
- Around line 228-270: The remove-image buttons in ProductReviews are accessible
but not distinguishable because every button in the images loop uses the same
aria-label. Update the image preview controls in the images.map block so each
remove button includes a unique label that identifies the specific image it will
delete, using the loop data (for example the index or a filename/description if
available) while keeping the removeImage handler behavior unchanged.
- Around line 192-213: The rating controls in ProductReviews use icon buttons
but do not expose the selected state to assistive tech. Update the Button inside
the star map in ProductReviews so each rating button includes aria-pressed based
on whether that star is selected, using the existing star/rating values to keep
the pressed state synchronized with the current rating.
In `@docs/pre-launch-audit.md`:
- Around line 5-8: The executive summary in the pre-launch audit is downplaying
a critical risk because it says there are no immediate blockers while the
ecommerce section flags payment/checkout verification as critical. Update the
summary text to explicitly call out the payment path or checkout verification as
the highest-risk item, keeping the wording consistent with the later ecommerce
assessment in the same document.
In `@docs/pre-launch-implementation-plan.md`:
- Around line 22-27: Update the SEO-002 plan to point at the actual route-level
implementation used in this branch instead of assuming metadata exports in page
components. Verify the server/client boundary for the `/checkout`, `/cart`, and
`/login` routes, and rewrite the Files/Approach guidance to reference the
correct `head.tsx` or other server-side route files rather than `app/*/page.tsx`
with `metadata`. Keep the task aligned with the route files currently used for
the noindex change so the next implementer lands in the right place.
In `@lib/data.ts`:
- Around line 3-9: The shared ATTAR_SIZES and PERFUME_SIZES arrays are being
reused by reference across many products, so a mutation in one product could
affect others. Update the product construction in the data module so each
product receives its own sizes array copy instead of the shared constants, using
the existing ATTAR_SIZES and PERFUME_SIZES definitions as the source but cloning
them wherever they are assigned to product.sizes.
In `@lib/mail/index.ts`:
- Around line 39-44: The subject fallback logic in sendContactNotificationEmail
duplicates normalization already done by the caller, so consolidate to a single
source of truth. Update the sendContactNotificationEmail flow in
lib/mail/index.ts to trust the normalized subject passed in (or centralize the
fallback in a shared helper used by both app/api/contact/route.ts and this mail
helper) so the "General Inquiry" default is handled consistently across all
callers.
In `@package.json`:
- Line 33: Move the shadcn package from dependencies to devDependencies in
package.json, since it is only used as a CLI via npx shadcn and not imported by
runtime code. Keep the existing version specifier unchanged, and update the
manifest so production installs do not include this tooling dependency.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2d51cae4-c7fd-4f87-96e4-d55e5709cc9f
⛔ Files ignored due to path filters (2)
app/favicon.icois excluded by!**/*.icopackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (40)
app/api/auth/me/route.tsapp/api/contact/route.tsapp/cart/head.tsxapp/checkout/head.tsxapp/checkout/page.tsxapp/faq/page.tsxapp/globals.cssapp/layout.tsxapp/login/head.tsxapp/shipping-policy/page.tsxapp/shop/ShopClient.tsxapp/size-guide/page.tsxapp/track-order/[id]/page.tsxcomponents.jsoncomponents/cart/CartSummary.tsxcomponents/home/FeaturedProducts.tsxcomponents/layout/ClientLayout.tsxcomponents/layout/Navbar.tsxcomponents/product/ProductGallery.tsxcomponents/product/ProductInfo.tsxcomponents/profile/ShippingAddresses.tsxcomponents/shop/ProductCard.tsxcomponents/shop/ProductGrid.tsxcomponents/shop/ProductReviews.tsxcomponents/ui/Button.tsxcomponents/ui/Toast.tsxcomponents/ui/checkbox.tsxcomponents/ui/input.tsxcomponents/ui/label.tsxcomponents/ui/select.tsxcomponents/ui/textarea.tsxdocs/pre-launch-audit.mddocs/pre-launch-implementation-plan.mdeslint.config.mjslib/data.tslib/mail/config.tslib/mail/index.tslib/utils.tspackage.jsonsanity/schemas/product.ts
💤 Files with no reviewable changes (2)
- app/shop/ShopClient.tsx
- components/product/ProductGallery.tsx
| export default function Head() { | ||
| return ( | ||
| <> | ||
| <meta name="robots" content="noindex, nofollow" /> | ||
| </> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## package.json / lockfile next version\n'
rg -n '"next"|next@|nextjs' package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true
printf '\n## app router head files\n'
git ls-files 'app/**/head.tsx' 'app/**/head.ts' 'app/**/layout.tsx' 'app/**/page.tsx' | sed -n '1,120p'
printf '\n## metadata usage\n'
rg -n 'generateMetadata|export const metadata|robots:' app --glob '!**/*.map' || trueRepository: benazeem/MS-Signature
Length of output: 2717
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## app/cart/layout.tsx\n'
cat -n app/cart/layout.tsx
printf '\n## app/login/layout.tsx\n'
cat -n app/login/layout.tsx
printf '\n## app/layout.tsx metadata section\n'
sed -n '1,120p' app/layout.tsx | cat -n
printf '\n## app/cart/head.tsx\n'
cat -n app/cart/head.tsx
printf '\n## app/checkout/head.tsx\n'
cat -n app/checkout/head.tsx
printf '\n## app/login/head.tsx\n'
cat -n app/login/head.tsxRepository: benazeem/MS-Signature
Length of output: 5721
🌐 Web query:
Next.js 16 App Router head.tsx supported special file metadata API app router docs
💡 Result:
In the Next.js App Router (including version 16), there is no head.tsx special file [1][2]. The head.js/head.tsx pattern was an early, experimental feature that has been deprecated and replaced by the Metadata API [2][3]. Instead of using a head.tsx file, you should manage document head elements using the built-in Metadata API [1][2]. Key points for managing metadata in the App Router: 1. Static Metadata: You can export a static metadata object from any layout.tsx or page.tsx file to define metadata such as title, description, and Open Graph tags [2][4]. This metadata cascades down the layout tree [2]. 2. Dynamic Metadata: For pages that require data fetching (e.g., blog posts), export an async generateMetadata function from your page.tsx or layout.tsx [2][4]. This function receives route parameters and can fetch data, allowing you to generate dynamic tags [2]. 3. Direct HTML tags: If you need to include static elements directly in the <head> (such as custom <script> or <link> tags that are not covered by the Metadata API), you can add them directly to your layout.tsx file [1]. Next.js will render these elements in the order they appear in your layout hierarchy [1]. 4. Migration: If you were previously using next/head (a Pages Router component), you should migrate fully to the Metadata API [2]. Using next/head in the App Router is not supported and will result in errors or conflicting tags [5][6][3].
Citations:
- 1: Clarifying valid ways to manage HTML head in App Router vercel/next.js#80725
- 2: https://toolsdock.io/blog/nextjs-metadata-guide
- 3: Head from next/head not working in NextJS 13 /app dir vercel/next.js#42708
- 4: https://github.com/vercel/next.js/blob/7a66008c/packages/next/src/lib/metadata/types/metadata-interface.ts
- 5: Head component not working vercel/next.js#53529
- 6: https://nextjs.org/docs/messages/no-head-element
Move these robots tags into Metadata API exports. app/cart/head.tsx, app/checkout/head.tsx, and app/login/head.tsx won’t render in this App Router setup, so the noindex, nofollow directive is being dropped. Add robots: { index: false, follow: false } to the route segment metadata instead (app/cart/layout.tsx, app/login/layout.tsx, and app/checkout/page.tsx or layout.tsx).
🤖 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 `@app/cart/head.tsx` around lines 1 - 7, The robots meta in Head is not being
rendered in this App Router setup, so move the noindex/nofollow directive into
the Metadata API for the affected routes. Remove the Head-based meta from the
cart, checkout, and login segments, and add robots: { index: false, follow:
false } in the appropriate route segment exports instead, using the existing
layout/page entry points like cart/layout, login/layout, and checkout/page or
checkout/layout.
| export const metadata: Metadata = { | ||
| title: "Attar Size Guide", | ||
| description: | ||
| "Learn which attar size is right for you. Compare 3ml, 6ml, and 12ml bottle sizes and how long they last.", | ||
| "Learn which attar and perfume size is right for you. Compare the current Sanity catalog sizes and how long they last.", | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Metadata title/description not updated for the new Perfume section.
The page heading is now "Attar and Perfume Size Guide" with a dedicated perfume section, but metadata.title still reads "Attar Size Guide" and the description doesn't mention perfume either.
✏️ Suggested fix
export const metadata: Metadata = {
- title: "Attar Size Guide",
+ title: "Attar and Perfume Size Guide",
description:
- "Learn which attar and perfume size is right for you. Compare the current Sanity catalog sizes and how long they last.",
+ "Learn which attar and perfume size is right for you. Compare the current attar and perfume catalog sizes and how long they last.",
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const metadata: Metadata = { | |
| title: "Attar Size Guide", | |
| description: | |
| "Learn which attar size is right for you. Compare 3ml, 6ml, and 12ml bottle sizes and how long they last.", | |
| "Learn which attar and perfume size is right for you. Compare the current Sanity catalog sizes and how long they last.", | |
| }; | |
| export const metadata: Metadata = { | |
| title: "Attar and Perfume Size Guide", | |
| description: | |
| "Learn which attar and perfume size is right for you. Compare the current attar and perfume catalog sizes and how long they last.", | |
| }; |
🤖 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 `@app/size-guide/page.tsx` around lines 28 - 32, Update the page metadata in
the size guide component so it matches the new “Attar and Perfume” content. In
the exported metadata object for the page, change the title from the attar-only
wording and revise the description to mention both attar and perfume, keeping it
aligned with the page heading and dedicated perfume section.
| if (category === "attar") { | ||
| switch (size.value) { | ||
| case "6ml": | ||
| return { | ||
| size: size.label, | ||
| title: "Discovery", | ||
| price: `From ${formatRupees(totalPrice)}`, | ||
| ideal: "First-time buyers, travel, sampling new scents", | ||
| applications: "~120-150 applications", | ||
| duration: "1-2 months (daily use)", | ||
| gift: false, | ||
| description: | ||
| "Compact and easy to carry. The 6ml size is ideal when you want to try a fragrance before moving up to a larger bottle.", | ||
| }; | ||
| case "12ml": | ||
| return { | ||
| size: size.label, | ||
| title: "Essential", | ||
| price: `From ${formatRupees(totalPrice)}`, | ||
| ideal: "Regular users, everyday wear, gifting", | ||
| applications: "~250-300 applications", | ||
| duration: "3-5 months (daily use)", | ||
| gift: true, | ||
| description: | ||
| "The balanced everyday option. 12ml gives you more room to wear a favourite fragrance often without moving to a collector size.", | ||
| popular: true, | ||
| }; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| size: size.label, | ||
| title: "Standard Spray", | ||
| price: `From ${formatRupees(totalPrice)}`, | ||
| ideal: "Everyday wear, gifting, travel", | ||
| applications: "~250-300 sprays", | ||
| duration: "2-4 months (daily use)", | ||
| gift: true, | ||
| description: | ||
| "Perfumes in the current catalog are stocked as spray bottles. Use this size when you want a clean, easy-to-wear daily fragrance.", | ||
| }; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## Files\n'
git ls-files app/size-guide/page.tsx sanity/schemas/product.ts
printf '\n## Relevant excerpts: app/size-guide/page.tsx\n'
nl -ba app/size-guide/page.tsx | sed -n '1,220p'
printf '\n## Relevant excerpts: sanity/schemas/product.ts\n'
nl -ba sanity/schemas/product.ts | sed -n '1,260p'
printf '\n## Search for attar size/value validation\n'
rg -n '"6ml"|"12ml"|size\.value|attar|fail-open|validator|validation' app/size-guide/page.tsx sanity/schemas/product.tsRepository: benazeem/MS-Signature
Length of output: 306
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## app/size-guide/page.tsx (around buildCard)\n'
awk 'NR>=1 && NR<=220 {printf "%4d %s\n", NR, $0}' app/size-guide/page.tsx
printf '\n## sanity/schemas/product.ts\n'
awk 'NR>=1 && NR<=260 {printf "%4d %s\n", NR, $0}' sanity/schemas/product.ts
printf '\n## Search for size validation and attar references\n'
rg -n '"6ml"|"12ml"|size\.value|attar|fail-open|validation|validator' app/size-guide/page.tsx sanity/schemas/product.tsRepository: benazeem/MS-Signature
Length of output: 17396
Handle unexpected attar sizes in the attar branch. Unknown attar sizes fall back to the generic “Standard Spray” copy, which is misleading for attar products. The Sanity rule can still allow bad data when category lookup fails, so this path can surface the wrong description; use an attar-specific fallback instead.
🤖 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 `@app/size-guide/page.tsx` around lines 78 - 121, Handle unexpected attar sizes
in the attar branch of the size guide helper so they do not fall through to the
generic spray copy. In the size mapping logic in app/size-guide/page.tsx, update
the attar-specific handling around the category === "attar" switch to return an
attar fallback object for unknown size.value cases instead of the shared
default; keep the fallback copy, title, applications, duration, and description
aligned with attar products, while preserving the existing 6ml and 12ml entries
in the same function.
| function buildGuideCards( | ||
| products: CatalogProduct[], | ||
| category: "attar" | "perfume", | ||
| fallbackCards: GuideCard[], | ||
| ) { | ||
| const categoryProducts = products.filter( | ||
| (product) => product.category === category, | ||
| ); | ||
| const basePrice = | ||
| categoryProducts[0]?.price ?? (category === "attar" ? 349 : 449); | ||
|
|
||
| const sizeMap = new Map< | ||
| string, | ||
| { label: string; value: string; price: number } | ||
| >(); | ||
|
|
||
| for (const product of categoryProducts) { | ||
| for (const size of product.sizes ?? []) { | ||
| if (!sizeMap.has(size.value)) { | ||
| sizeMap.set(size.value, size); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const sizes = Array.from(sizeMap.values()).sort((left, right) => { | ||
| const leftPriority = sizeLabelPriority.indexOf(left.value); | ||
| const rightPriority = sizeLabelPriority.indexOf(right.value); | ||
|
|
||
| if (leftPriority !== rightPriority) { | ||
| return ( | ||
| (leftPriority === -1 ? Number.MAX_SAFE_INTEGER : leftPriority) - | ||
| (rightPriority === -1 ? Number.MAX_SAFE_INTEGER : rightPriority) | ||
| ); | ||
| } | ||
|
|
||
| return parseSizeValue(left.value) - parseSizeValue(right.value); | ||
| }); | ||
|
|
||
| if (sizes.length === 0) { | ||
| return fallbackCards; | ||
| } | ||
|
|
||
| return sizes.map((size) => buildCard(category, size, basePrice)); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
"From ₹X" pricing derived from the first product in the category, not the minimum.
basePrice uses categoryProducts[0]?.price, and sizeMap keeps the first-encountered size object per unique value across all products in the category. If products in the same category have different base prices, the guide's "From ₹X" figures reflect an arbitrary first product rather than an actual minimum/representative price, which could be misleading as the catalog grows beyond today's uniformly-priced set.
💡 Suggested fix to use the true minimum
- const basePrice =
- categoryProducts[0]?.price ?? (category === "attar" ? 349 : 449);
+ const basePrice =
+ categoryProducts.length > 0
+ ? Math.min(...categoryProducts.map((p) => p.price))
+ : (category === "attar" ? 349 : 449);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function buildGuideCards( | |
| products: CatalogProduct[], | |
| category: "attar" | "perfume", | |
| fallbackCards: GuideCard[], | |
| ) { | |
| const categoryProducts = products.filter( | |
| (product) => product.category === category, | |
| ); | |
| const basePrice = | |
| categoryProducts[0]?.price ?? (category === "attar" ? 349 : 449); | |
| const sizeMap = new Map< | |
| string, | |
| { label: string; value: string; price: number } | |
| >(); | |
| for (const product of categoryProducts) { | |
| for (const size of product.sizes ?? []) { | |
| if (!sizeMap.has(size.value)) { | |
| sizeMap.set(size.value, size); | |
| } | |
| } | |
| } | |
| const sizes = Array.from(sizeMap.values()).sort((left, right) => { | |
| const leftPriority = sizeLabelPriority.indexOf(left.value); | |
| const rightPriority = sizeLabelPriority.indexOf(right.value); | |
| if (leftPriority !== rightPriority) { | |
| return ( | |
| (leftPriority === -1 ? Number.MAX_SAFE_INTEGER : leftPriority) - | |
| (rightPriority === -1 ? Number.MAX_SAFE_INTEGER : rightPriority) | |
| ); | |
| } | |
| return parseSizeValue(left.value) - parseSizeValue(right.value); | |
| }); | |
| if (sizes.length === 0) { | |
| return fallbackCards; | |
| } | |
| return sizes.map((size) => buildCard(category, size, basePrice)); | |
| } | |
| function buildGuideCards( | |
| products: CatalogProduct[], | |
| category: "attar" | "perfume", | |
| fallbackCards: GuideCard[], | |
| ) { | |
| const categoryProducts = products.filter( | |
| (product) => product.category === category, | |
| ); | |
| const basePrice = | |
| categoryProducts.length > 0 | |
| ? Math.min(...categoryProducts.map((p) => p.price)) | |
| : (category === "attar" ? 349 : 449); | |
| const sizeMap = new Map< | |
| string, | |
| { label: string; value: string; price: number } | |
| >(); | |
| for (const product of categoryProducts) { | |
| for (const size of product.sizes ?? []) { | |
| if (!sizeMap.has(size.value)) { | |
| sizeMap.set(size.value, size); | |
| } | |
| } | |
| } | |
| const sizes = Array.from(sizeMap.values()).sort((left, right) => { | |
| const leftPriority = sizeLabelPriority.indexOf(left.value); | |
| const rightPriority = sizeLabelPriority.indexOf(right.value); | |
| if (leftPriority !== rightPriority) { | |
| return ( | |
| (leftPriority === -1 ? Number.MAX_SAFE_INTEGER : leftPriority) - | |
| (rightPriority === -1 ? Number.MAX_SAFE_INTEGER : rightPriority) | |
| ); | |
| } | |
| return parseSizeValue(left.value) - parseSizeValue(right.value); | |
| }); | |
| if (sizes.length === 0) { | |
| return fallbackCards; | |
| } | |
| return sizes.map((size) => buildCard(category, size, basePrice)); | |
| } |
🤖 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 `@app/size-guide/page.tsx` around lines 123 - 166, The “From ₹X” value in
buildGuideCards is taken from the first product in a category instead of the
lowest available price, so update the pricing source to use the true minimum
across categoryProducts before passing it into buildCard. Also make sure the
size selection logic in buildGuideCards still deduplicates by size value, but
the displayed price is derived from the category’s minimum product price rather
than categoryProducts[0]?.price.
| useEffect(() => { | ||
| if (email) { | ||
| fetchAddresses(); | ||
| queueMicrotask(() => { | ||
| fetchAddresses(); | ||
| }); | ||
| } else { | ||
| setLoading(false); | ||
| queueMicrotask(() => { | ||
| setLoading(false); | ||
| }); | ||
| } | ||
| }, [email, fetchAddresses]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if similar queueMicrotask deferral pattern is documented anywhere
rg -n 'queueMicrotask' -C2 --type=ts --type=tsx
rg -n 'queueMicrotask' docs/Repository: benazeem/MS-Signature
Length of output: 187
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== file list ==\n'
git ls-files 'components/profile/ShippingAddresses.tsx' 'components/**' | sed -n '1,20p'
printf '\n== outline ==\n'
ast-grep outline components/profile/ShippingAddresses.tsx --view expanded || true
printf '\n== queueMicrotask search ==\n'
rg -n 'queueMicrotask' .
printf '\n== ShippingAddresses.tsx excerpt ==\n'
cat -n components/profile/ShippingAddresses.tsx | sed -n '1,220p'Repository: benazeem/MS-Signature
Length of output: 8730
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== fetchAddresses references ==\n'
rg -n 'fetchAddresses|setLoading\(|setAddresses\(' components/profile/ShippingAddresses.tsx
printf '\n== nearby effects in file ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path('components/profile/ShippingAddresses.tsx')
text = p.read_text()
for needle in ['useEffect(() => {', 'queueMicrotask', 'fetchAddresses', 'setLoading(false)']:
print(f'-- {needle} --')
idx = text.find(needle)
if idx != -1:
start = max(0, text.rfind('\n', 0, idx-120))
end = text.find('\n', idx+len(needle)+200)
print(text[start:end if end!=-1 else len(text)])
print()
PYRepository: benazeem/MS-Signature
Length of output: 2152
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== queueMicrotask call sites ==\n'
for f in app/checkout/page.tsx components/shop/ProductReviews.tsx components/profile/ShippingAddresses.tsx; do
printf '\n-- %s --\n' "$f"
cat -n "$f" | sed -n '1,180p' | rg -n 'queueMicrotask|useEffect|fetch|setLoading|setState|cleanup|return \(\) =>'
done
printf '\n== ShippingAddresses cleanup-related patterns ==\n'
rg -n 'return \(\) =>|AbortController|isMounted|cancel|cleanup' components/profile/ShippingAddresses.tsxRepository: benazeem/MS-Signature
Length of output: 2478
Remove the queueMicrotask wrapper here. useEffect already runs after commit, so this extra tick is redundant; if you need to avoid a post-unmount state update, add cleanup/abort handling instead.
🤖 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 `@components/profile/ShippingAddresses.tsx` around lines 92 - 102, The
ShippingAddresses useEffect currently wraps fetchAddresses and setLoading(false)
in queueMicrotask, which adds an unnecessary extra tick. Remove the
queueMicrotask wrapper inside ShippingAddresses and let the effect call
fetchAddresses or setLoading directly, and if there is a concern about updates
after unmount, handle that with cleanup or abort logic in the effect itself.
| if (href) { | ||
| return ( | ||
| <Link href={href} className={classes} id={id} target={target} rel={rel}> | ||
| <Link href={href} className={classes} target={target} rel={rel}> | ||
| {children} | ||
| </Link> | ||
| ); | ||
| ) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Button component ==\n'
git ls-files 'components/ui/Button.tsx' 'app/checkout/page.tsx' 'components/home/FeaturedProducts.tsx'
printf '\n-- Button.tsx outline --\n'
ast-grep outline components/ui/Button.tsx --view expanded || true
printf '\n-- Button.tsx relevant lines --\n'
sed -n '1,220p' components/ui/Button.tsx | cat -n
printf '\n== Button call sites ==\n'
rg -n "<Button[^>]*href=|id=" app/checkout/page.tsx components/home/FeaturedProducts.tsx || true
printf '\n-- checkout excerpt --\n'
sed -n '1,220p' app/checkout/page.tsx | cat -n | sed -n '1,220p'
printf '\n-- featured excerpt --\n'
sed -n '1,220p' components/home/FeaturedProducts.tsx | cat -n | sed -n '1,220p'Repository: benazeem/MS-Signature
Length of output: 16375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Search for Link props/typing in repo ==\n'
rg -n "ComponentPropsWithoutRef<typeof ButtonPrimitive>|ButtonProps|href\\s*\\?|target=|rel=" components/ui app components -g '!**/*.map' || true
printf '\n== Search for other Button usage with href and extra props ==\n'
rg -n "<Button[^>]*href=.*(id=|onClick=|aria-|data-)" . -g '!**/*.map' || trueRepository: benazeem/MS-Signature
Length of output: 2669
Forward props in the href branch
<Link> only receives className, target, and rel; ...props is dropped. That strips id, handlers, and aria-* from link-styled buttons, including existing href call sites in this PR.
Suggested fix
- <Link href={href} className={classes} target={target} rel={rel}>
+ <Link href={href} className={classes} target={target} rel={rel} {...props}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (href) { | |
| return ( | |
| <Link href={href} className={classes} id={id} target={target} rel={rel}> | |
| <Link href={href} className={classes} target={target} rel={rel}> | |
| {children} | |
| </Link> | |
| ); | |
| ) | |
| } | |
| if (href) { | |
| return ( | |
| <Link href={href} className={classes} target={target} rel={rel} {...props}> | |
| {children} | |
| </Link> | |
| ) | |
| } |
🤖 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 `@components/ui/Button.tsx` around lines 65 - 71, The Button component’s href
branch drops forwarded props, so link-styled buttons lose attributes like id,
event handlers, and aria-* from the Button API. Update the href path in Button
to pass through the remaining props to Link while still setting className,
target, and rel, matching the non-href branch behavior.
| if (!catRef) return true; | ||
| try { | ||
| const client = context.getClient({ apiVersion: "2024-01-01" }); | ||
| const cat = await client.getDocument(catRef); | ||
| const attarAllowed = ["6ml", "12ml"]; | ||
| const perfumeAllowed = ["30ml"]; | ||
|
|
||
| if (cat?.slug?.current === "attar" || (cat?.name && String(cat.name).toLowerCase() === "attar")) { | ||
| const invalid = (sizes || []).filter((s: unknown) => { | ||
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | ||
| return typeof val !== "string" || !attarAllowed.includes(val); | ||
| }); | ||
| if (invalid.length) return "Attar products can only have sizes 6ml or 12ml."; | ||
| } | ||
|
|
||
| if (cat?.slug?.current === "perfume" || (cat?.name && String(cat.name).toLowerCase() === "perfume")) { | ||
| const invalid = (sizes || []).filter((s: unknown) => { | ||
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | ||
| return typeof val !== "string" || !perfumeAllowed.includes(val); | ||
| }); | ||
| if (invalid.length) return "Perfume products can only have size 30ml."; | ||
| } | ||
|
|
||
| return true; | ||
| } catch { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Validator fails open on any lookup error.
If catRef is missing, the category fetch throws, or the category document lacks a recognizable slug/name, the validator returns true and silently allows any size values. A transient network blip or a mis-set category reference bypasses the intended enforcement entirely, and there's no logged trace of the bypass for later auditing.
🛡️ Suggested tweak to fail safer / log bypass
} catch {
- return true;
+ console.error("sizes validation: failed to fetch category", catRef);
+ return true;
}Also worth checking that a missing catRef (line 106) is truly a valid "no restriction" case rather than a data issue that should itself be flagged.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!catRef) return true; | |
| try { | |
| const client = context.getClient({ apiVersion: "2024-01-01" }); | |
| const cat = await client.getDocument(catRef); | |
| const attarAllowed = ["6ml", "12ml"]; | |
| const perfumeAllowed = ["30ml"]; | |
| if (cat?.slug?.current === "attar" || (cat?.name && String(cat.name).toLowerCase() === "attar")) { | |
| const invalid = (sizes || []).filter((s: unknown) => { | |
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | |
| return typeof val !== "string" || !attarAllowed.includes(val); | |
| }); | |
| if (invalid.length) return "Attar products can only have sizes 6ml or 12ml."; | |
| } | |
| if (cat?.slug?.current === "perfume" || (cat?.name && String(cat.name).toLowerCase() === "perfume")) { | |
| const invalid = (sizes || []).filter((s: unknown) => { | |
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | |
| return typeof val !== "string" || !perfumeAllowed.includes(val); | |
| }); | |
| if (invalid.length) return "Perfume products can only have size 30ml."; | |
| } | |
| return true; | |
| } catch { | |
| return true; | |
| } | |
| if (!catRef) return true; | |
| try { | |
| const client = context.getClient({ apiVersion: "2024-01-01" }); | |
| const cat = await client.getDocument(catRef); | |
| const attarAllowed = ["6ml", "12ml"]; | |
| const perfumeAllowed = ["30ml"]; | |
| if (cat?.slug?.current === "attar" || (cat?.name && String(cat.name).toLowerCase() === "attar")) { | |
| const invalid = (sizes || []).filter((s: unknown) => { | |
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | |
| return typeof val !== "string" || !attarAllowed.includes(val); | |
| }); | |
| if (invalid.length) return "Attar products can only have sizes 6ml or 12ml."; | |
| } | |
| if (cat?.slug?.current === "perfume" || (cat?.name && String(cat.name).toLowerCase() === "perfume")) { | |
| const invalid = (sizes || []).filter((s: unknown) => { | |
| const val = typeof s === "object" && s !== null && "value" in s ? (s as { value?: unknown }).value : undefined; | |
| return typeof val !== "string" || !perfumeAllowed.includes(val); | |
| }); | |
| if (invalid.length) return "Perfume products can only have size 30ml."; | |
| } | |
| return true; | |
| } catch { | |
| console.error("sizes validation: failed to fetch category", catRef); | |
| return true; | |
| } |
🤖 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 `@sanity/schemas/product.ts` around lines 106 - 132, The size validator in the
product schema currently fails open in the catRef/category lookup path, so
invalid sizes can slip through whenever the fetch fails or the category is
unrecognized. Update the validation logic around the anonymous validator in
product.ts to treat missing catRef or lookup errors as a hard validation failure
(or an explicit flagged state), and only return true when the category is known
and matches the allowed size rules for attar/perfume. Also add logging or a
clear error path in the catch block so bypasses are visible for auditing.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@app/globals.css`:
- Around line 362-367: Remove the redundant background-color declaration in the
:root block of globals.css so the variable is assigned only once. Update the
root theme variables near the repeated background-color line, keeping the first
background-color: var(--color-primary) and deleting the duplicate later
declaration to avoid unnecessary duplication.
- Around line 360-425: The theme variables in :root are incomplete, so utilities
like bg-primary, bg-accent, and bg-popover have no values because app/layout.tsx
never applies the .dark class. Update the :root block in globals.css to define
the missing tokens (--primary, --primary-foreground, --accent, --popover, and
--popover-foreground) to match the dark theme values, or alternatively ensure
the root element gets the .dark class. Keep the existing .dark definitions in
sync with those token names.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6f301210-728a-4131-b251-bd17f51f4007
📒 Files selected for processing (9)
app/globals.cssapp/layout.tsxcomponents/home/BrandStory.tsxcomponents/home/Hero.tsxcomponents/home/WhyChooseUs.tsxcomponents/layout/ClientLayout.tsxcomponents/layout/Navbar.tsxcomponents/ui/Button.tsxlib/utils.ts
✅ Files skipped from review due to trivial changes (4)
- components/home/WhyChooseUs.tsx
- components/home/BrandStory.tsx
- components/home/Hero.tsx
- components/layout/Navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- components/ui/Button.tsx
| :root { | ||
| min-height: 100%; | ||
| background-color: var(--color-primary); | ||
| --background: #070707; | ||
| --foreground: #f0f0f0; | ||
| --card: #0d0d0d; | ||
| --card-foreground: #ededed; | ||
| background-color: var(--color-primary); | ||
| --secondary: #121212; | ||
| --secondary-foreground: #ededed; | ||
| --muted: #101010; | ||
| --muted-foreground: #999999; | ||
| --accent-foreground: #ededed; | ||
| --destructive: oklch(0.577 0.245 27.325); | ||
| --border: #222222; | ||
| --input: #222222; | ||
| --ring: #d4af37; | ||
| --chart-1: #d4af37; | ||
| --chart-2: #f5d98a; | ||
| --chart-3: #999999; | ||
| --chart-4: #2a2a2a; | ||
| --chart-5: #1a1a1a; | ||
| --radius: 0.625rem; | ||
| --sidebar: #0d0d0d; | ||
| --sidebar-foreground: #ededed; | ||
| --sidebar-primary: #d4af37; | ||
| --sidebar-primary-foreground: #0a0a0a; | ||
| --sidebar-accent: #121212; | ||
| --sidebar-accent-foreground: #ededed; | ||
| --sidebar-border: #222222; | ||
| --sidebar-ring: #d4af37; | ||
| } | ||
|
|
||
| .dark { | ||
| --background: #070707; | ||
| --foreground: #f0f0f0; | ||
| --card: #0d0d0d; | ||
| --card-foreground: #ededed; | ||
| --popover: #0d0d0d; | ||
| --popover-foreground: #ededed; | ||
| --primary: #d4af37; | ||
| --primary-foreground: #0a0a0a; | ||
| --secondary: #121212; | ||
| --secondary-foreground: #ededed; | ||
| --muted: #101010; | ||
| --muted-foreground: #999999; | ||
| --accent: #121212; | ||
| --accent-foreground: #ededed; | ||
| --destructive: oklch(0.704 0.191 22.216); | ||
| --border: #222222; | ||
| --input: #222222; | ||
| --ring: #d4af37; | ||
| --chart-1: #d4af37; | ||
| --chart-2: #f5d98a; | ||
| --chart-3: #999999; | ||
| --chart-4: #2a2a2a; | ||
| --chart-5: #1a1a1a; | ||
| --sidebar: #0d0d0d; | ||
| --sidebar-foreground: #ededed; | ||
| --sidebar-primary: #d4af37; | ||
| --sidebar-primary-foreground: #0a0a0a; | ||
| --sidebar-accent: #121212; | ||
| --sidebar-accent-foreground: #ededed; | ||
| --sidebar-border: #222222; | ||
| --sidebar-ring: #d4af37; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Is a `.dark` class applied to html/body anywhere, or a theme provider used?
echo "--- .dark class on root element / theme provider ---"
rg -nP 'className=.*\bdark\b|dark:class|next-themes|ThemeProvider|classList\.(add|toggle)\(.?dark' -g '!node_modules'
echo "--- tokens defined in :root vs .dark ---"
rg -nP '^\s*--(primary|primary-foreground|accent|popover|popover-foreground)\b' app/globals.css
echo "--- usages of the affected semantic utilities ---"
rg -nP 'bg-(primary|accent|popover)|text-(primary|accent|popover)-foreground' -g '!node_modules' -g '*.tsx'Repository: benazeem/MS-Signature
Length of output: 212
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- files of interest ---"
git ls-files | rg '^(app/(layout|globals)\.css|.*theme.*|.*provider.*|.*layout.*\.tsx)$'
echo "--- root layout ---"
if [ -f app/layout.tsx ]; then
cat -n app/layout.tsx
fi
echo "--- theme token definitions in app/globals.css ---"
rg -n '^(?:\s*--(primary|primary-foreground|accent|popover|popover-foreground)\b|:root|\.dark\b|`@theme` inline)' app/globals.css
echo "--- .dark / theme provider references ---"
rg -n 'next-themes|ThemeProvider|classList\.(add|toggle)\(\s*[`"'"'"']dark|className=.*\bdark\b|<html[^>]*className|<body[^>]*className' app
echo "--- semantic utility usages ---"
rg -n 'bg-(primary|accent|popover)(/[\d]+)?|text-(primary|accent|popover)-foreground' app --glob '*.tsx' --glob '*.ts' --glob '*.jsx' --glob '*.js'Repository: benazeem/MS-Signature
Length of output: 10107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- app/globals.css around theme mappings and token blocks ---"
sed -n '300,430p' app/globals.css | cat -n
echo "--- all custom-property declarations for theme colors in app/globals.css ---"
rg -n '^\s*--(background|foreground|card|card-foreground|popover|popover-foreground|primary|primary-foreground|secondary|secondary-foreground|muted|muted-foreground|accent|accent-foreground|destructive|border|input|ring|chart-[1-5]|sidebar|sidebar-foreground|sidebar-primary|sidebar-primary-foreground|sidebar-accent|sidebar-accent-foreground|sidebar-border|sidebar-ring)\b' app/globals.cssRepository: benazeem/MS-Signature
Length of output: 6959
Add the missing theme tokens to :root
app/layout.tsx does not apply a .dark class, so --primary, --primary-foreground, --accent, --popover, and --popover-foreground never resolve outside .dark. That leaves bg-primary / bg-accent / bg-popover utilities without values. Add those tokens to :root or switch the root element to .dark.
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 362-362: Duplicate property "background-color" (declaration-block-no-duplicate-properties)
(declaration-block-no-duplicate-properties)
[error] 367-367: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 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 `@app/globals.css` around lines 360 - 425, The theme variables in :root are
incomplete, so utilities like bg-primary, bg-accent, and bg-popover have no
values because app/layout.tsx never applies the .dark class. Update the :root
block in globals.css to define the missing tokens (--primary,
--primary-foreground, --accent, --popover, and --popover-foreground) to match
the dark theme values, or alternatively ensure the root element gets the .dark
class. Keep the existing .dark definitions in sync with those token names.
| background-color: var(--color-primary); | ||
| --background: #070707; | ||
| --foreground: #f0f0f0; | ||
| --card: #0d0d0d; | ||
| --card-foreground: #ededed; | ||
| background-color: var(--color-primary); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the duplicate background-color declaration. :root sets background-color: var(--color-primary) twice (lines 362 and 367); the second is redundant.
🧹 Proposed cleanup
:root {
min-height: 100%;
background-color: var(--color-primary);
--background: `#070707`;
--foreground: `#f0f0f0`;
--card: `#0d0d0d`;
--card-foreground: `#ededed`;
- background-color: var(--color-primary);
--secondary: `#121212`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| background-color: var(--color-primary); | |
| --background: #070707; | |
| --foreground: #f0f0f0; | |
| --card: #0d0d0d; | |
| --card-foreground: #ededed; | |
| background-color: var(--color-primary); | |
| background-color: var(--color-primary); | |
| --background: `#070707`; | |
| --foreground: `#f0f0f0`; | |
| --card: `#0d0d0d`; | |
| --card-foreground: `#ededed`; |
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 362-362: Duplicate property "background-color" (declaration-block-no-duplicate-properties)
(declaration-block-no-duplicate-properties)
[error] 367-367: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 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 `@app/globals.css` around lines 362 - 367, Remove the redundant
background-color declaration in the :root block of globals.css so the variable
is assigned only once. Update the root theme variables near the repeated
background-color line, keeping the first background-color: var(--color-primary)
and deleting the duplicate later declaration to avoid unnecessary duplication.
Source: Linters/SAST tools
Summary by CodeRabbit