Skip to content

Use Shadcn - #50

Merged
damianlegawiec merged 12 commits into
mainfrom
chore/shacdn
Mar 10, 2026
Merged

Use Shadcn#50
damianlegawiec merged 12 commits into
mainfrom
chore/shacdn

Conversation

@damianlegawiec

@damianlegawiec damianlegawiec commented Mar 9, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Unified UI toolkit (buttons, badges, dialogs/sheets, dropdowns, selects, input groups, fields, popovers, product/category images, quantity picker, skeletons) and reusable components across the site.
  • Improvements

    • Visual refresh and consistent iconography across store, cart, checkout, account, and product pages.
    • Improved image handling with graceful fallbacks and adjusted image quality.
    • Refined light/dark theme tokens and accessible form controls.
  • Chores

    • Dependency and config updates to support the new UI stack.

@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a shadcn/Radix-based design system and UI primitives, introduces ProductImage/CategoryImage, tokenizes Tailwind theme, migrates many icons to lucide-react, removes SVGR handling, updates deps/config, and replaces numerous custom UI pieces with the new primitives; also changes customer register API to accept a params object.

Changes

Cohort / File(s) Summary
Config & deps
components.json, package.json, next.config.ts, vitest.config.ts
New UI configuration, dependency additions (radix, lucide-react, cva/clsx/tailwind-merge, shadcn, tw-animate-css), removal of SVGR transform/loader, and Next image quality adjustments.
Global styles & theme
src/app/globals.css
Introduces tokenized light/dark theme variables, imports shadcn & tw-animate-css, and replaces root color resets with extensive CSS custom properties.
UI primitives (new public API)
src/components/ui/*
Adds many Radix-based, cva-backed primitives and composites (button, badge, checkbox, dialog, dropdown-menu, field, input-group, input, label, popover, radio-group, select, separator, sheet, textarea, etc.) — new exported components and variant configs.
Composed overlays & menus
src/components/ui/dialog.tsx, src/components/ui/sheet.tsx, src/components/ui/dropdown-menu.tsx, src/components/ui/popover.tsx
New Radix-wrapped Dialog/Sheet/Dropdown/Popover suites replacing many bespoke modals/drawers/menus; exported components and subcomponents added.
Image & product helpers
src/components/ui/product-image.tsx, src/components/ui/category-image.tsx, src/components/navigation/CategoryBanner.tsx
Adds ProductImage and CategoryImage with graceful fallbacks and a CategoryBanner component used by pages.
Input & form primitives
src/components/ui/field.tsx, src/components/ui/input-group.tsx, src/components/ui/input.tsx, src/components/ui/label.tsx, src/components/ui/select.tsx
Standardized form primitives (Field, FieldLabel, Input, InputGroup, Select) introduced and used to replace many raw inputs/selects.
Small UI components & skeletons
src/components/ui/quantity-picker.tsx, src/components/products/ProductCardSkeleton.tsx, src/components/products/ProductGridSkeleton.tsx
New small composables (QuantityPicker, skeletons) and ProductCardSkeleton introduced for consistent loading and cart/product interactions.
Icon migration / removal
src/components/icons/index.ts, many src/components/**, src/app/**
Removes local SVG barrel exports and migrates usages across the app to lucide-react icons.
Cart, checkout & checkout components
src/components/cart/CartDrawer.tsx, src/components/checkout/*, src/app/.../(checkout)/*
Replaces custom drawers/modals and raw controls with Sheet/Dialog and new UI primitives (Button, Field, Input, Select, QuantityPicker, ProductImage); mainly presentation/composition changes.
Product listing, filters & taxonomies
src/components/products/*, src/components/products/filters/*, src/app/.../taxonomies/page.tsx
Filters refactored to DropdownMenu/Radio/Checkbox primitives, images replaced with ProductImage/CategoryImage, skeletons added, and generateMetadata exported for categories page.
Account, auth & forms
src/app/.../(storefront)/account/**, src/app/.../register/page.tsx
Forms migrated to Field/Input/Button composites, icons moved to lucide, register flow now sends first_name/last_name and uses a params object for registration.
Layout & navigation
src/components/layout/*, src/components/navigation/*, src/app/.../page.tsx
Header/Footer/CountrySwitcher updated, logo switched to an image, CategoryBanner added, and nav icons/buttons refactored to new primitives.
Utilities & lib changes
src/lib/utils.ts, src/lib/utils/color-map.ts, src/lib/data/customer.ts
Adds cn helper (clsx + tailwind-merge), normalizes color-map key parsing, and changes customer.register to accept a params object.
Tests & config updates
src/lib/data/__tests__/customer.test.ts, vitest.config.ts
Tests updated for new register signature; vitest config removes SVGR pre-transform.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Redesign product filters #48 — Overlaps on product filters and shared filter utilities; touches the same filter components and APIs.
  • Bump SDK to 0.7.1 #49 — Related SDK/type and dependency updates that align with import and type changes (e.g., CreditCard aliasing).

Poem

🐰 I nibbled SVGs and found Lucide light,
Buttons burrowed in, dialogs snug and bright.
Radix built tunnels where modals now play,
Tailwind seeded tokens to color the day.
Hops of joy — this UI feels just right!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Use Shadcn' is vague and does not clearly convey the scope or primary purpose of the extensive changes. While the PR does integrate shadcn components, the title lacks specificity about the comprehensive refactoring it represents. Consider a more descriptive title such as 'Integrate shadcn UI components and refactor icon system' or 'Replace custom components with shadcn UI library' to better communicate the primary change to reviewers scanning the pull request history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/shacdn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/components/products/MediaGallery.tsx (2)

77-85: ⚠️ Potential issue | 🟡 Minor

Set type="button" on the gallery controls.

These buttons currently default to submit. If MediaGallery is ever rendered inside a product form, clicking thumbnails or lightbox controls will submit that form unexpectedly.

Possible fix
               <button
+                type="button"
                 key={image.id}
                 onClick={() => setSelectedIndex(index)}
                 className={`relative w-20 h-20 flex-shrink-0 rounded-xl overflow-hidden border-2 transition-colors bg-gray-100 ${
@@
           <button
+            type="button"
             className="absolute top-4 right-4 text-white p-2 hover:bg-white/10 rounded-lg transition-colors"
             onClick={() => setIsZoomed(false)}
             aria-label="Close lightbox"
@@
               <button
+                type="button"
                 className="absolute left-4 top-1/2 -translate-y-1/2 text-white p-2 hover:bg-white/10 rounded-lg transition-colors"
                 onClick={(e) => {
@@
               <button
+                type="button"
                 className="absolute right-4 top-1/2 -translate-y-1/2 text-white p-2 hover:bg-white/10 rounded-lg transition-colors"
                 onClick={(e) => {

Also applies to: 111-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/MediaGallery.tsx` around lines 77 - 85, The gallery
buttons in MediaGallery are missing an explicit type and default to "submit",
causing unintended form submissions; update every button element in MediaGallery
(e.g., the thumbnail buttons that call setSelectedIndex and the lightbox control
buttons referenced around the other button block) to include type="button" so
clicks don't submit surrounding forms.

41-66: ⚠️ Potential issue | 🟠 Major

Make the zoom trigger keyboard-accessible.

This is still a clickable div, so keyboard users can't open the lightbox. Please use a real button here, or add equivalent button semantics and keyboard handling.

Possible fix
-      <div
-        className="relative aspect-square bg-gray-100 rounded-xl overflow-hidden cursor-zoom-in"
+      <button
+        type="button"
+        className="relative aspect-square bg-gray-100 rounded-xl overflow-hidden cursor-zoom-in focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
         onClick={() => mainImageUrl && setIsZoomed(true)}
+        aria-label={`Zoom image of ${selectedImage?.alt || productName}`}
       >
         {mainImageUrl ? (
           <Image
             src={mainImageUrl}
             alt={selectedImage?.alt || productName}
@@
         {mainImageUrl && (
           <div className="absolute bottom-4 right-4 bg-white/80 backdrop-blur-sm px-3 py-1.5 rounded-lg text-sm text-gray-600 flex items-center gap-1.5">
             <ZoomIn className="w-4 h-4" />
             Click to zoom
           </div>
         )}
-      </div>
+      </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/MediaGallery.tsx` around lines 41 - 66, The zoom
trigger is a clickable div that isn’t keyboard-accessible; update the element
that currently uses onClick={() => mainImageUrl && setIsZoomed(true)} (and the
visual hint containing <ZoomIn /> / "Click to zoom") to be a real <button> or to
add full button semantics: render a <button> (or element with role="button")
that is focusable (tabIndex=0), has an accessible label (aria-label like "Open
image zoom"), and handles activation via onClick and onKeyDown (treat
Enter/Space the same as click) to call setIsZoomed(true) only when mainImageUrl
is present; ensure styling and className are preserved and that the element is
keyboard and screen-reader friendly.
package.json (1)

40-40: ⚠️ Potential issue | 🟡 Minor

Remove @svgr/webpack as it's unused. The package is not referenced in next.config.ts or anywhere else in the codebase, confirming it's an orphaned dependency from the previous icon system. Remove it from package.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 40, Remove the unused dependency "@svgr/webpack" from
package.json and update the lockfile by running your package manager install
(e.g., npm install or yarn install) so the dependency is removed from
package-lock.json / yarn.lock; search for any remaining references to
"@svgr/webpack" to confirm it's not used before committing the change.
src/components/products/filters/MobileFilterDrawer.tsx (1)

42-46: ⚠️ Potential issue | 🟠 Major

This effect can clobber staged edits while the drawer is open.

If activeFilters changes from the parent while the sheet is open, this immediately overwrites stagedFilters and discards the user's in-progress selections. Please switch to a reset-on-open/keyed-child pattern instead of syncing props back into local state with useEffect. As per coding guidelines "Avoid using useEffect to reset state when props change; use component key prop to reset state or compute initial state from props".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/filters/MobileFilterDrawer.tsx` around lines 42 - 46,
The useEffect in MobileFilterDrawer that sets stagedFilters when activeFilters
changes can overwrite user edits while the sheet is open; remove that effect and
stop syncing props into local state. Instead, implement a reset-on-open or
keyed-child pattern: initialize stagedFilters from activeFilters only when the
drawer is created (use initial state) and ensure the drawer is re-mounted when
you want to reset by giving the MobileFilterDrawer (or its internal sheet child)
a key derived from activeFilters (e.g., a filtersVersion or serialized
activeFilters) or only reset stagedFilters when opening the drawer from
closed-to-open; update references to isOpen, setStagedFilters, stagedFilters and
activeFilters accordingly and remove the useEffect that currently syncs
activeFilters into stagedFilters.
🧹 Nitpick comments (10)
src/components/products/ProductCard.tsx (1)

4-4: Consider using a more direct import alias.

The alias ImageIcon as ImagePlaceholder works, but lucide-react provides an ImageOff icon that might be more semantically appropriate for a "no image" placeholder scenario. Alternatively, keeping ImageIcon as-is without aliasing would be clearer.

💡 Optional: Use ImageOff for better semantics
-import { ImageIcon as ImagePlaceholder } from "lucide-react";
+import { ImageOff as ImagePlaceholder } from "lucide-react";

Or simply use without alias:

-import { ImageIcon as ImagePlaceholder } from "lucide-react";
+import { ImageIcon } from "lucide-react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/ProductCard.tsx` at line 4, The import currently
aliases ImageIcon to ImagePlaceholder; change this to a clearer import by either
importing ImageIcon directly (remove the alias and update all usages of
ImagePlaceholder to ImageIcon) or switch to lucide-react's ImageOff (import
ImageOff and replace all ImagePlaceholder usages with ImageOff) so the
placeholder icon name better matches semantics; update the JSX where
ImagePlaceholder is used accordingly.
src/app/[country]/[locale]/(storefront)/taxonomies/page.tsx (1)

22-22: Consider adding generateMetadata for SEO.

This page lacks a generateMetadata function. While not directly related to the icon migration changes, the coding guidelines require dynamic SEO metadata generation for pages.

📝 Example implementation
import type { Metadata } from "next";

export async function generateMetadata({ params }: CategoriesPageProps): Promise<Metadata> {
  const { locale } = await params;
  return {
    title: "Categories",
    description: "Browse all product categories",
    // Add locale-specific metadata as needed
  };
}

As per coding guidelines: "Use generateMetadata function to dynamically generate SEO metadata for pages based on route params"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/taxonomies/page.tsx at line 22, Add
a generateMetadata export to this page to provide dynamic SEO metadata based on
route params: implement an exported async function named generateMetadata that
accepts the same params type as CategoriesPage (CategoriesPageProps) and returns
a Metadata object (title, description, and any locale-specific fields); ensure
it extracts locale from params and returns appropriate strings so the page has
dynamic SEO metadata alongside the existing export default async function
CategoriesPage.
src/components/ui/radio-group.tsx (1)

8-19: Consider adding explicit return types per coding guidelines.

The RadioGroup and RadioGroupItem functions lack explicit return types. While TypeScript can infer the return type, the coding guidelines specify explicit return types for functions.

✨ Proposed fix to add explicit return types
 function RadioGroup({
   className,
   ...props
-}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
+}: React.ComponentProps<typeof RadioGroupPrimitive.Root>): React.JSX.Element {
   return (
     <RadioGroupPrimitive.Root
       data-slot="radio-group"
       className={cn("grid w-full gap-2", className)}
       {...props}
     />
   );
 }

 function RadioGroupItem({
   className,
   ...props
-}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
+}: React.ComponentProps<typeof RadioGroupPrimitive.Item>): React.JSX.Element {
   return (

Also applies to: 21-42

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/radio-group.tsx` around lines 8 - 19, The RadioGroup and
RadioGroupItem functions lack explicit return types; update their signatures to
include an explicit JSX return type (e.g., add ": JSX.Element" or
"React.ReactElement" to the function declarations for RadioGroup and
RadioGroupItem) so the components conform to the project's TypeScript style
guide and make the return type explicit.
src/lib/utils.ts (1)

4-6: Add explicit return type annotation.

Per coding guidelines, functions should have explicit return types. The cn function should declare its return type.

Suggested fix
-export function cn(...inputs: ClassValue[]) {
+export function cn(...inputs: ClassValue[]): string {
   return twMerge(clsx(inputs));
 }

As per coding guidelines: "Use strict TypeScript type checking; define explicit return types for functions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/utils.ts` around lines 4 - 6, The cn function lacks an explicit
return type—add a return type annotation to its signature (e.g., declare
cn(...inputs: ClassValue[]): string) so the function explicitly returns a string
produced by clsx and twMerge; update the cn function declaration to include ":
string" while keeping the existing parameters and body referencing ClassValue,
clsx, and twMerge.
src/components/ui/textarea.tsx (1)

5-16: Add explicit return type annotation.

Per coding guidelines, functions should have explicit return types.

Suggested fix
-function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element {
   return (

As per coding guidelines: "Use strict TypeScript type checking; define explicit return types for functions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/textarea.tsx` around lines 5 - 16, Add an explicit return
type to the Textarea component: update the function signature for Textarea to
include a return type such as JSX.Element or React.ReactElement (e.g., function
Textarea(...): JSX.Element) so it conforms to the project's strict TypeScript
rules; locate the Textarea function and annotate its signature accordingly while
leaving props and implementation unchanged.
src/app/[country]/[locale]/(checkout)/layout.tsx (1)

19-21: Consider adding priority prop for above-the-fold logo.

The header logo is visible immediately on page load. Adding priority={true} will preload the image and improve Largest Contentful Paint (LCP).

Suggested improvement
-        <Image src="/spree.png" alt="Spree Store" width={90} height={32} />
+        <Image src="/spree.png" alt="Spree Store" width={90} height={32} priority />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(checkout)/layout.tsx around lines 19 - 21, The
header logo Image used inside the Link (the JSX with Link and Image) should be
marked as a high-priority above-the-fold asset; update the Image element
referenced here to include the Next.js priority prop (e.g., add priority={true}
or priority) alongside the existing src, alt, width, and height so the logo is
preloaded to improve LCP.
src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx (1)

162-173: Consider adding generateMetadata for SEO.

Per coding guidelines, page files should use generateMetadata to dynamically generate SEO metadata. This could be added to improve discoverability of the gift cards page.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/account/gift-cards/page.tsx around
lines 162 - 173, Add an exported generateMetadata function alongside the
existing GiftCardsPage component that returns an object with title and
description (and other SEO fields as needed) so the page has dynamic metadata;
you can call getGiftCards or use route params to construct a descriptive title
like "Gift Cards" and a helpful description, then export async function
generateMetadata(...) { return { title: '...', description: '...' } }; keep the
client-side GiftCardsPage and its useEffect/getGiftCards usage unchanged and
ensure generateMetadata is exported at module scope.
src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx (1)

24-284: Consider adding generateMetadata for SEO.

This dynamic route page lacks a generateMetadata export. While order confirmation pages may have limited SEO value, adding basic metadata improves consistency and can help with page titles in browser tabs/history.

💡 Example metadata export
import type { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: "Order Confirmation",
    robots: { index: false, follow: false }, // Prevent indexing of order pages
  };
}

As per coding guidelines: "Use generateMetadata function to dynamically generate SEO metadata for pages based on route params."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(checkout)/order-placed/[id]/page.tsx around
lines 24 - 284, Add a generateMetadata export to this page to supply basic SEO
info: import type { Metadata } from "next" and implement export async function
generateMetadata(): Promise<Metadata> that returns at least a title like "Order
Confirmation" and robots: { index: false, follow: false } (optionally use
params/order data to include order number or customer name); place this
alongside the OrderPlacedPage export so Next picks it up for the dynamic route.
src/components/layout/Header.tsx (1)

24-24: Consider adding priority prop for the logo image.

The logo is above the fold in the sticky header and likely contributes to LCP. Adding priority ensures it's preloaded.

💡 Suggested change
-            <Image src="/spree.png" alt="Spree Store" width={90} height={32} />
+            <Image src="/spree.png" alt="Spree Store" width={90} height={32} priority />

As per coding guidelines: "Use Next.js Image component with optimization props (priority, placeholder, blurDataURL) instead of HTML img tags."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/Header.tsx` at line 24, The logo Image in Header.tsx is
missing the Next.js performance props; update the Image component (the JSX
element rendering <Image src="/spree.png" alt="Spree Store" width={90}
height={32} />) to include priority to preload it for LCP (and optionally add
placeholder and blurDataURL if a low-quality placeholder is available); ensure
you only add the priority prop (priority={true} or just priority) and, if adding
placeholder/blurDataURL, provide a valid blurDataURL string.
src/components/products/filters/AvailabilityDropdownContent.tsx (1)

2-5: Use radio items for single-select availability filter.

The selected prop holds a single AvailabilityStatus, but lines 26-44 render checkbox items, which announce a multi-select control. The actual behavior toggles between one selection and none (mutually exclusive). Replace DropdownMenuCheckboxItem with DropdownMenuRadioGroup and DropdownMenuRadioItem to match the single-select semantics. See SortDropdownContent.tsx for the same pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/filters/AvailabilityDropdownContent.tsx` around lines
2 - 5, The AvailabilityDropdownContent renders checkbox items while its selected
prop is a single AvailabilityStatus; change the control to a radio group to
match single-select semantics by replacing DropdownMenuCheckboxItem usages with
a DropdownMenuRadioGroup that wraps DropdownMenuRadioItem entries, mirroring the
pattern used in SortDropdownContent.tsx; update imports to bring in
DropdownMenuRadioGroup and DropdownMenuRadioItem and ensure selection logic in
AvailabilityDropdownContent (the selected prop and onSelect handler) maps to the
radio group's value/onValueChange so a single option is chosen or cleared
correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[country]/[locale]/(storefront)/account/page.tsx:
- Around line 85-95: The email and password inputs lost browser autofill
metadata; restore name and autoComplete props on the Input components (the one
with id="email" using value={email} and setEmail, and the other input using
id="password" and setPassword) so password managers can match credentials—add
name="email" autoComplete="email" to the email Input and name="current-password"
autoComplete="current-password" (or "new-password" if for signup) to the
password Input.

In `@src/app/`[country]/[locale]/(storefront)/cart/page.tsx:
- Around line 110-136: The per-item controls render identical buttons and allow
a no-op decrement; update the Button usages for the increment, decrement and
remove actions to include unique aria-labels (e.g., `aria-label={`Decrease
quantity for ${item.id}`}`, `aria-label={`Increase quantity for ${item.id}`}`,
`aria-label={`Remove item ${item.id}`}`) so each control is self-describing, and
set the decrement Button to disabled when `item.quantity === 1` to prevent
dispatching `updateItem(item.id, 1)`; keep calls to `updateItem(item.id,
item.quantity - 1)`, `updateItem(item.id, item.quantity + 1)`, and
`handleRemove(item)` unchanged otherwise.

In `@src/app/globals.css`:
- Line 10: The CSS defines a circular/undefined variable (--font-sans:
var(--font-sans)) while the body uses --font-geist and html is assigned a
Tailwind font class, causing inconsistent fonts; fix by making the variables
consistent: either set --font-sans to reference --font-geist (replace the
current --font-sans declaration with a direct mapping to --font-geist) or update
body to use var(--font-sans) so both html and body use the same variable; locate
and update the declarations for --font-sans, --font-geist, the body rule, and
the html font assignment to ensure a single authoritative font variable is used.

In `@src/components/cart/CartDrawer.tsx`:
- Around line 244-247: In the CartDrawer component update the user-visible
shipping copy: replace the misspelled string "Ccalculated on checkout" (the
second <span> in the div with className "flex justify-between items-center")
with the correct text "Calculated on checkout" so the UI shows the proper
spelling.
- Around line 65-69: The SheetContent is rendering SheetTitle without a
SheetDescription which triggers Radix Dialog accessibility warnings; update the
SheetContent usage in CartDrawer (the component that renders SheetTitle but no
SheetDescription) to explicitly pass aria-describedby={undefined} so the prop is
forwarded to the underlying Dialog.Content and suppresses the warning, i.e., add
the aria-describedby={undefined} attribute to the SheetContent element that
currently has side="right" className="w-full max-w-md flex flex-col p-0 gap-0"
showCloseButton={false}.

In `@src/components/checkout/AddressEditModal.tsx`:
- Around line 141-147: In AddressEditModal.tsx the Cancel Button renders as a
native button inside the form and will default to type="submit"; update the
Cancel Button (the Button with variant="outline" and onClick={onClose}) to
explicitly include type="button" so it does not submit the form when clicked and
only runs onClose.

In `@src/components/checkout/AddressFormFields.tsx`:
- Around line 105-160: Add explicit ids on the rendered controls and set
FieldLabel htmlFor to those ids so labels are programmatically associated: give
the country SelectTrigger an id like `${idPrefix}-country` and set the first
FieldLabel htmlFor to that id; for the state block give the SelectTrigger used
when hasStates an id like `${idPrefix}-state-select` and set the second
FieldLabel htmlFor to that id; for the fallback Input keep its existing id
`${idPrefix}-state` and also set the second FieldLabel htmlFor to that id (and
ensure the loading disabled SelectTrigger also receives an id
`${idPrefix}-state-select`), so FieldLabel, SelectTrigger and Input ids match
and restore accessibility.

In `@src/components/checkout/CouponCode.tsx`:
- Around line 7-8: The coupon input is placeholder-only and lacks an accessible
name; update the CouponCode component so the Field/Input pair provides a
programmatic label (e.g., render a visible or visually-hidden <label> tied to
the Input via htmlFor/id or add aria-label/aria-labelledby on the Input) so
screen readers can identify the control; locate the Field and Input usage in
CouponCode.tsx (both around the import of Field/Input and the input block at
lines ~96-116) and ensure the Input has a stable id and corresponding label or
aria attribute reflecting "Coupon code" (or similar) and remove reliance on
placeholder for accessibility.

In `@src/components/checkout/PaymentStep.tsx`:
- Around line 289-291: The Edit and Back buttons inside the PaymentStep
component are rendered within a form with onSubmit={handleSubmit} and currently
default to type="submit", so update the Button instances that call onBack (the
one with onClick={onBack} and the other Back button in the same PaymentStep) to
explicitly set type="button" to prevent them from submitting the form; locate
the Button elements that use the onBack handler and add type="button" to each.

In `@src/components/layout/CountrySwitcher.tsx`:
- Around line 87-90: Replace the use of onClick on each DropdownMenuItem with
onSelect so keyboard users can activate items; specifically, remove the onClick
prop on the DropdownMenuItem instances (the ones keyed by c.iso) and use
onSelect={() => handleCountrySelect(c)} instead so handleCountrySelect is
invoked for both pointer and keyboard activation.

In `@src/components/navigation/Breadcrumbs.tsx`:
- Line 51: Breadcrumb link styling in Breadcrumbs.tsx currently uses
className="text-primary hover:text-primary" so hover shows no change; update the
class on the breadcrumb link element (the JSX element using
className="text-primary hover:text-primary") to use a distinct hover color such
as className="text-primary-500 hover:text-primary-700" (or your theme's darker
primary variant) so the link visually responds on hover.

In `@src/components/products/filters/FilterDropdown.tsx`:
- Around line 43-45: The aria-haspopup value on the dropdown trigger in
FilterDropdown.tsx is incorrect for a Radix DropdownMenu; update the attribute
from aria-haspopup="listbox" to aria-haspopup="menu" (or remove aria-haspopup
entirely) on the trigger element so it matches the menu semantics used by the
Radix DropdownMenu component (look for the JSX element setting aria-expanded and
aria-haspopup in the FilterDropdown component).

In `@src/components/products/filters/PriceDropdownContent.tsx`:
- Around line 32-45: The filter is using DropdownMenuCheckboxItem which gives
checkbox (multi-select) semantics but the UI is single-select; replace the
checkbox items with a radio group: wrap the list in DropdownMenuRadioGroup and
render each option with DropdownMenuRadioItem instead of
DropdownMenuCheckboxItem, preserving keys (bucket.id), labels (bucket.label) and
selection logic via isSelected and onPriceChange (call onPriceChange(undefined,
undefined) to clear, or onPriceChange(bucket.min, bucket.max) to select). Also
remove onCheckedChange/onSelect handlers incompatible with radio items and
ensure the radio group's value/checked state is driven by the same selected
bucket state.

In `@src/components/ui/checkbox.tsx`:
- Line 16: The Tailwind selectors using `data-checked:` are incorrect for Radix
UI state attributes; locate the class string in the Checkbox component (the
string containing `data-checked:border-primary data-checked:bg-primary
data-checked:text-primary-foreground` in checkbox.tsx) and replace each
`data-checked:` usage with the arbitrary data selector `data-[state=checked]:`
(e.g., `data-[state=checked]:border-primary`, `data-[state=checked]:bg-primary`,
etc.); do the same in the RadioGroup component where `data-checked:` selectors
appear (radio-group.tsx line with those selectors) so styles target Radix’s
`data-state="checked"` correctly.

In `@src/components/ui/dialog.tsx`:
- Around line 38-44: The Tailwind state selectors on DialogPrimitive.Overlay are
using non-existent boolean attributes (data-open/data-closed) so the animation
classes never apply; update the className construction in the
DialogPrimitive.Overlay (and similar uses) to use Radix's data-state attribute
selectors, e.g., replace data-open/data-closed selectors with
data-[state=open]:... and data-[state=closed]:... (so change occurrences in the
cn call that build animation classes for DialogPrimitive.Overlay and any zoom
variants), keeping the existing className and props spread logic intact.

In `@src/components/ui/input-group.tsx`:
- Around line 58-63: The onClick handler in src/components/ui/input-group.tsx
currently focuses only an <input> element, so clicks on addons for
textarea-based groups won't focus the control; update the handler (the onClick
closure) to locate the first focusable form control inside the group instead of
hard-coding "input" — e.g., use a selector that includes "textarea", "select",
contenteditable or role="textbox" (or otherwise detect focusable elements) and
call focus() on the found element; ensure you still skip clicks that originated
inside a button via the existing closest("button") check.

In `@src/components/ui/popover.tsx`:
- Around line 58-64: PopoverTitle is declared with React.ComponentProps<"h2">
but currently returns a <div>, losing heading semantics; update the PopoverTitle
component to render an actual <h2> element (preserving
data-slot="popover-title", className via cn("font-medium", className), and
spreading ...props) so the rendered element matches its typing and provides
correct accessibility/semantic behavior.

---

Outside diff comments:
In `@package.json`:
- Line 40: Remove the unused dependency "@svgr/webpack" from package.json and
update the lockfile by running your package manager install (e.g., npm install
or yarn install) so the dependency is removed from package-lock.json /
yarn.lock; search for any remaining references to "@svgr/webpack" to confirm
it's not used before committing the change.

In `@src/components/products/filters/MobileFilterDrawer.tsx`:
- Around line 42-46: The useEffect in MobileFilterDrawer that sets stagedFilters
when activeFilters changes can overwrite user edits while the sheet is open;
remove that effect and stop syncing props into local state. Instead, implement a
reset-on-open or keyed-child pattern: initialize stagedFilters from
activeFilters only when the drawer is created (use initial state) and ensure the
drawer is re-mounted when you want to reset by giving the MobileFilterDrawer (or
its internal sheet child) a key derived from activeFilters (e.g., a
filtersVersion or serialized activeFilters) or only reset stagedFilters when
opening the drawer from closed-to-open; update references to isOpen,
setStagedFilters, stagedFilters and activeFilters accordingly and remove the
useEffect that currently syncs activeFilters into stagedFilters.

In `@src/components/products/MediaGallery.tsx`:
- Around line 77-85: The gallery buttons in MediaGallery are missing an explicit
type and default to "submit", causing unintended form submissions; update every
button element in MediaGallery (e.g., the thumbnail buttons that call
setSelectedIndex and the lightbox control buttons referenced around the other
button block) to include type="button" so clicks don't submit surrounding forms.
- Around line 41-66: The zoom trigger is a clickable div that isn’t
keyboard-accessible; update the element that currently uses onClick={() =>
mainImageUrl && setIsZoomed(true)} (and the visual hint containing <ZoomIn /> /
"Click to zoom") to be a real <button> or to add full button semantics: render a
<button> (or element with role="button") that is focusable (tabIndex=0), has an
accessible label (aria-label like "Open image zoom"), and handles activation via
onClick and onKeyDown (treat Enter/Space the same as click) to call
setIsZoomed(true) only when mainImageUrl is present; ensure styling and
className are preserved and that the element is keyboard and screen-reader
friendly.

---

Nitpick comments:
In `@src/app/`[country]/[locale]/(checkout)/layout.tsx:
- Around line 19-21: The header logo Image used inside the Link (the JSX with
Link and Image) should be marked as a high-priority above-the-fold asset; update
the Image element referenced here to include the Next.js priority prop (e.g.,
add priority={true} or priority) alongside the existing src, alt, width, and
height so the logo is preloaded to improve LCP.

In `@src/app/`[country]/[locale]/(checkout)/order-placed/[id]/page.tsx:
- Around line 24-284: Add a generateMetadata export to this page to supply basic
SEO info: import type { Metadata } from "next" and implement export async
function generateMetadata(): Promise<Metadata> that returns at least a title
like "Order Confirmation" and robots: { index: false, follow: false }
(optionally use params/order data to include order number or customer name);
place this alongside the OrderPlacedPage export so Next picks it up for the
dynamic route.

In `@src/app/`[country]/[locale]/(storefront)/account/gift-cards/page.tsx:
- Around line 162-173: Add an exported generateMetadata function alongside the
existing GiftCardsPage component that returns an object with title and
description (and other SEO fields as needed) so the page has dynamic metadata;
you can call getGiftCards or use route params to construct a descriptive title
like "Gift Cards" and a helpful description, then export async function
generateMetadata(...) { return { title: '...', description: '...' } }; keep the
client-side GiftCardsPage and its useEffect/getGiftCards usage unchanged and
ensure generateMetadata is exported at module scope.

In `@src/app/`[country]/[locale]/(storefront)/taxonomies/page.tsx:
- Line 22: Add a generateMetadata export to this page to provide dynamic SEO
metadata based on route params: implement an exported async function named
generateMetadata that accepts the same params type as CategoriesPage
(CategoriesPageProps) and returns a Metadata object (title, description, and any
locale-specific fields); ensure it extracts locale from params and returns
appropriate strings so the page has dynamic SEO metadata alongside the existing
export default async function CategoriesPage.

In `@src/components/layout/Header.tsx`:
- Line 24: The logo Image in Header.tsx is missing the Next.js performance
props; update the Image component (the JSX element rendering <Image
src="/spree.png" alt="Spree Store" width={90} height={32} />) to include
priority to preload it for LCP (and optionally add placeholder and blurDataURL
if a low-quality placeholder is available); ensure you only add the priority
prop (priority={true} or just priority) and, if adding placeholder/blurDataURL,
provide a valid blurDataURL string.

In `@src/components/products/filters/AvailabilityDropdownContent.tsx`:
- Around line 2-5: The AvailabilityDropdownContent renders checkbox items while
its selected prop is a single AvailabilityStatus; change the control to a radio
group to match single-select semantics by replacing DropdownMenuCheckboxItem
usages with a DropdownMenuRadioGroup that wraps DropdownMenuRadioItem entries,
mirroring the pattern used in SortDropdownContent.tsx; update imports to bring
in DropdownMenuRadioGroup and DropdownMenuRadioItem and ensure selection logic
in AvailabilityDropdownContent (the selected prop and onSelect handler) maps to
the radio group's value/onValueChange so a single option is chosen or cleared
correctly.

In `@src/components/products/ProductCard.tsx`:
- Line 4: The import currently aliases ImageIcon to ImagePlaceholder; change
this to a clearer import by either importing ImageIcon directly (remove the
alias and update all usages of ImagePlaceholder to ImageIcon) or switch to
lucide-react's ImageOff (import ImageOff and replace all ImagePlaceholder usages
with ImageOff) so the placeholder icon name better matches semantics; update the
JSX where ImagePlaceholder is used accordingly.

In `@src/components/ui/radio-group.tsx`:
- Around line 8-19: The RadioGroup and RadioGroupItem functions lack explicit
return types; update their signatures to include an explicit JSX return type
(e.g., add ": JSX.Element" or "React.ReactElement" to the function declarations
for RadioGroup and RadioGroupItem) so the components conform to the project's
TypeScript style guide and make the return type explicit.

In `@src/components/ui/textarea.tsx`:
- Around line 5-16: Add an explicit return type to the Textarea component:
update the function signature for Textarea to include a return type such as
JSX.Element or React.ReactElement (e.g., function Textarea(...): JSX.Element) so
it conforms to the project's strict TypeScript rules; locate the Textarea
function and annotate its signature accordingly while leaving props and
implementation unchanged.

In `@src/lib/utils.ts`:
- Around line 4-6: The cn function lacks an explicit return type—add a return
type annotation to its signature (e.g., declare cn(...inputs: ClassValue[]):
string) so the function explicitly returns a string produced by clsx and
twMerge; update the cn function declaration to include ": string" while keeping
the existing parameters and body referencing ClassValue, clsx, and twMerge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 211892d4-e917-4e12-a27b-87363957f651

📥 Commits

Reviewing files that changed from the base of the PR and between 414a9e3 and 8fd19fd.

⛔ Files ignored due to path filters (34)
  • package-lock.json is excluded by !**/package-lock.json
  • public/spree.png is excluded by !**/*.png
  • src/components/icons/arrow-left.svg is excluded by !**/*.svg
  • src/components/icons/check-circle-solid.svg is excluded by !**/*.svg
  • src/components/icons/check-circle.svg is excluded by !**/*.svg
  • src/components/icons/check-solid.svg is excluded by !**/*.svg
  • src/components/icons/check.svg is excluded by !**/*.svg
  • src/components/icons/chevron-down.svg is excluded by !**/*.svg
  • src/components/icons/chevron-left.svg is excluded by !**/*.svg
  • src/components/icons/chevron-right.svg is excluded by !**/*.svg
  • src/components/icons/clipboard-copy.svg is excluded by !**/*.svg
  • src/components/icons/close.svg is excluded by !**/*.svg
  • src/components/icons/credit-card.svg is excluded by !**/*.svg
  • src/components/icons/eye-slash.svg is excluded by !**/*.svg
  • src/components/icons/eye.svg is excluded by !**/*.svg
  • src/components/icons/filter.svg is excluded by !**/*.svg
  • src/components/icons/gift.svg is excluded by !**/*.svg
  • src/components/icons/grid.svg is excluded by !**/*.svg
  • src/components/icons/home.svg is excluded by !**/*.svg
  • src/components/icons/image-placeholder.svg is excluded by !**/*.svg
  • src/components/icons/info-circle.svg is excluded by !**/*.svg
  • src/components/icons/lightning-bolt.svg is excluded by !**/*.svg
  • src/components/icons/lock.svg is excluded by !**/*.svg
  • src/components/icons/map-pin.svg is excluded by !**/*.svg
  • src/components/icons/minus.svg is excluded by !**/*.svg
  • src/components/icons/plus.svg is excluded by !**/*.svg
  • src/components/icons/search-plus.svg is excluded by !**/*.svg
  • src/components/icons/search.svg is excluded by !**/*.svg
  • src/components/icons/shopping-bag.svg is excluded by !**/*.svg
  • src/components/icons/sign-out.svg is excluded by !**/*.svg
  • src/components/icons/spinner.svg is excluded by !**/*.svg
  • src/components/icons/support.svg is excluded by !**/*.svg
  • src/components/icons/user.svg is excluded by !**/*.svg
  • src/components/icons/x-circle-solid.svg is excluded by !**/*.svg
📒 Files selected for processing (67)
  • components.json
  • next.config.ts
  • package.json
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/addresses/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/credit-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/gift-cards/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/profile/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(storefront)/page.tsx
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/app/[country]/[locale]/(storefront)/taxonomies/page.tsx
  • src/app/global-error.tsx
  • src/app/globals.css
  • src/components/cart/CartDrawer.tsx
  • src/components/checkout/AddressEditModal.tsx
  • src/components/checkout/AddressFormFields.tsx
  • src/components/checkout/AddressSelector.tsx
  • src/components/checkout/AddressStep.tsx
  • src/components/checkout/CouponCode.tsx
  • src/components/checkout/DeliveryStep.tsx
  • src/components/checkout/OrderSummary.tsx
  • src/components/checkout/PaymentStep.tsx
  • src/components/icons/index.ts
  • src/components/layout/CountrySwitcher.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Header.tsx
  • src/components/navigation/Breadcrumbs.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/components/products/ProductCarousel.tsx
  • src/components/products/ProductListingLayout.tsx
  • src/components/products/VariantPicker.tsx
  • src/components/products/filters/AvailabilityDropdownContent.tsx
  • src/components/products/filters/FilterChips.tsx
  • src/components/products/filters/FilterDropdown.tsx
  • src/components/products/filters/MobileFilterDrawer.tsx
  • src/components/products/filters/OptionDropdownContent.tsx
  • src/components/products/filters/PriceDropdownContent.tsx
  • src/components/products/filters/ProductFilters.tsx
  • src/components/products/filters/SortDropdownContent.tsx
  • src/components/search/SearchBar.tsx
  • src/components/ui/badge.tsx
  • src/components/ui/button.tsx
  • src/components/ui/checkbox.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/components/ui/field.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/input.tsx
  • src/components/ui/label.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/radio-group.tsx
  • src/components/ui/select.tsx
  • src/components/ui/separator.tsx
  • src/components/ui/sheet.tsx
  • src/components/ui/textarea.tsx
  • src/lib/utils.ts
  • src/lib/utils/filters.ts
  • vitest.config.ts
💤 Files with no reviewable changes (2)
  • next.config.ts
  • src/components/icons/index.ts

Comment thread src/app/[country]/[locale]/(storefront)/account/page.tsx
Comment thread src/app/[country]/[locale]/(storefront)/cart/page.tsx Outdated
Comment thread src/app/globals.css Outdated
Comment thread src/components/cart/CartDrawer.tsx
Comment thread src/components/cart/CartDrawer.tsx
Comment thread src/components/products/filters/PriceDropdownContent.tsx Outdated
Comment thread src/components/ui/checkbox.tsx Outdated
Comment thread src/components/ui/dialog.tsx
Comment thread src/components/ui/input-group.tsx
Comment thread src/components/ui/popover.tsx
damianlegawiec and others added 3 commits March 9, 2026 21:40
- Unify header navigation icons (cart, account) with consistent Button ghost style
- Extract reusable QuantityPicker component used across PDP, cart drawer, and cart page
- Extract ProductCardSkeleton for DRY skeleton loading across product grid and carousel
- Create CategoryImage wrapper to fix Server→Client component serialization
- Create CategoryBanner component for taxonomy pages
- Create ProductImage component with fallback placeholder icons
- Add cursor-pointer to all interactive Button elements
- Fix filter dropdown alignment (Color/Size vs Price/Availability)
- Fix OrderSummary quantity badge clipping
- Fix payment step loading spinner style
- Fix quantity picker button gap (rounded-md vs rounded-xl mismatch)
- Add disabled state styling for quantity picker minus button
- Fix Next.js Image aspect ratio warning on logo
- Remove deprecated resolveConditions from turbopack config
- Migrate shadcn/ui components to latest patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
next.config.ts (1)

4-44: ⚠️ Potential issue | 🟡 Minor

Remove the stale SVG type declaration in src/types/svg.d.ts.

The SVGR webpack transform has been removed from next.config.ts, but src/types/svg.d.ts still declares import Icon from "./icon.svg" as a React component. While no code currently imports SVGs as components, this stale type declaration should be removed to prevent confusion and keep the type contract aligned with the bundler configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.ts` around lines 4 - 44, Remove the stale SVG component type
declaration that asserts imports like `import Icon from "./icon.svg"` as React
components: open the svg type file (the `svg.d.ts` declaration that contains the
`import Icon from "./icon.svg"` / module declaration for "*.svg") and delete the
entire declaration or remove the module augmentation that exposes SVGs as React
components so the types match the removed SVGR transform; ensure no other
type-only imports remain that assert SVGs are components.
src/components/search/SearchBar.tsx (1)

149-168: ⚠️ Potential issue | 🟠 Major

Give the search combobox a programmatic label.

The new InputGroup keeps this field placeholder-only. placeholder="Search..." is not a reliable accessible name, so screen-reader users get an unnamed combobox for a primary navigation control.

💡 Proposed fix
           <InputGroupInput
             ref={inputRef}
             type="search"
+            aria-label="Search products"
             value={query}
             onChange={(e) => {
               setQuery(e.target.value);
               setIsOpen(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/search/SearchBar.tsx` around lines 149 - 168, The combobox
InputGroupInput (ref inputRef) currently relies on placeholder text and lacks a
programmatic label; add an accessible name by providing either
aria-label="Search" or aria-labelledby that points to a visible label element
for the search input, and ensure the InputGroupInput keeps existing ARIA
properties (role="combobox", aria-expanded, aria-controls,
aria-activedescendant, aria-autocomplete) so screen readers get a proper label
for the control.
♻️ Duplicate comments (2)
src/components/products/VariantPicker.tsx (1)

164-167: ⚠️ Potential issue | 🟠 Major

Make the variant controls explicit non-submit buttons.

These buttons only change the selected variant. Inside a form, <button> defaults to submit behavior unless type="button" is set, so selecting an option can submit instead of just updating the picker. (developer.mozilla.org)

Proposed fix
                   return (
                     <button
                       key={value}
+                      type="button"
                       onClick={() => handleOptionSelect(optionType.id, value)}
                       disabled={!isAvailable}
                       title={optionValue?.presentation || value}
@@
-                    <Button
+                    <Button
                       key={value}
+                      type="button"
                       variant={isSelected ? "secondary" : "outline"}
                       onClick={() => handleOptionSelect(optionType.id, value)}
                       disabled={!isAvailable}

Also applies to: 203-207

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/VariantPicker.tsx` around lines 164 - 167, The
variant selection buttons inside VariantPicker are missing explicit type
attributes so they act as submit buttons when rendered inside a form; update the
<button> elements used for option selection (the ones invoking
handleOptionSelect(optionType.id, value) and the other similar buttons later in
the file) to include type="button" to prevent form submission and keep them as
non-submit controls.
src/components/ui/quantity-picker.tsx (1)

30-49: ⚠️ Potential issue | 🟠 Major

Make the quantity buttons explicit non-submit controls.

This is a reusable input-like control. Inside a form, buttons submit by default unless they opt out explicitly, so increment/decrement can trigger form submission instead of just changing the quantity. (developer.mozilla.org)

Proposed fix
       <Button
+        type="button"
         variant="ghost"
         size={buttonSize}
         className="rounded-l-xl rounded-r-none disabled:opacity-30"
@@
       <Button
+        type="button"
         variant="ghost"
         size={buttonSize}
         className="rounded-r-xl rounded-l-none disabled:opacity-30"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/quantity-picker.tsx` around lines 30 - 49, The
increment/decrement controls are regular Button components that, when used
inside a form, can act as submit buttons by default; update both Button usages
(the one with onDecrement and the one with onIncrement) to explicitly opt out of
form submission by adding type="button" to each Button element so they behave as
non-submit controls while preserving existing props (variant, size, className,
disabled, aria-label, and handlers).
🧹 Nitpick comments (7)
src/components/products/ProductGridSkeleton.tsx (1)

1-1: Use the @ alias for this import.

This new relative import goes against the repo’s TS/TSX import convention. Please switch it to the absolute alias form for consistency with the rest of the codebase.

As per coding guidelines, "Use absolute imports with @ alias (e.g., @/components/..., @/lib/...) instead of relative imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/ProductGridSkeleton.tsx` at line 1, The import in
ProductGridSkeleton.tsx uses a relative path for ProductCardSkeleton; change it
to the repo alias form using the @ prefix (e.g., import { ProductCardSkeleton }
from "@/components/products/ProductCardSkeleton") so it follows the project's
absolute import convention and matches other TSX imports.
src/components/ui/popover.tsx (1)

8-10: Add explicit return types to these shared component wrappers.

These exported components currently rely on inference. In a shared UI module, explicit return types make type drift easier to catch and align with the repo TS rule.

Suggested change
 function Popover({
   ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
+}: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element {
   return <PopoverPrimitive.Root data-slot="popover" {...props} />;
 }

 function PopoverTrigger({
   ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
+}: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element {
   return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
 }

 function PopoverContent({
   className,
   align = "center",
   sideOffset = 4,
   ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
+}: React.ComponentProps<typeof PopoverPrimitive.Content>): React.JSX.Element {
   return (
     <PopoverPrimitive.Portal>
       <PopoverPrimitive.Content
         data-slot="popover-content"
         align={align}
         sideOffset={sideOffset}
         className={cn(
           "z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
           className,
         )}
         {...props}
       />
     </PopoverPrimitive.Portal>
   );
 }

 function PopoverAnchor({
   ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
+}: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element {
   return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
 }

-function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
+function PopoverHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element {
   return (
     <div
       data-slot="popover-header"
       className={cn("flex flex-col gap-0.5 text-sm", className)}
       {...props}
     />
   );
 }

-function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
+function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">): React.JSX.Element {
   return (
     <h2
       data-slot="popover-title"
       className={cn("font-medium", className)}
       {...props}
     />
   );
 }

 function PopoverDescription({
   className,
   ...props
-}: React.ComponentProps<"p">) {
+}: React.ComponentProps<"p">): React.JSX.Element {
   return (
     <p
       data-slot="popover-description"
       className={cn("text-muted-foreground", className)}
       {...props}
     />
   );
 }

As per coding guidelines, **/*.ts{,x}: Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type.

Also applies to: 14-16, 20-25, 42-48, 58-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/popover.tsx` around lines 8 - 10, The exported component
wrappers (e.g., Popover, PopoverTrigger, PopoverContent, PopoverClose,
PopoverPortal) currently rely on inferred return types—update each function
signature to include an explicit React return type such as React.ReactElement |
null (or JSX.Element | null) while keeping the existing props types (e.g.,
React.ComponentProps<typeof PopoverPrimitive.Root>); locate the functions by
name (Popover, PopoverTrigger, PopoverContent, PopoverClose, PopoverPortal) and
change their declarations to include the explicit return type to satisfy the
repo TS rule.
src/app/globals.css (1)

14-24: Two distinct "primary" color systems may cause visual inconsistency.

The numbered primary scale (lines 14-24) uses blue hues (#0077ff#001d4d), while the semantic --color-primary (line 49) maps to --primary which is defined as oklch(0.205 0 0) — a near-black color.

Components using bg-primary-500 will render blue, while components using bg-primary will render near-black. If this is intentional (legacy support + new shadcn), consider documenting the distinction. If not, align the color values.

Also applies to: 48-49, 98-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` around lines 14 - 24, There are two conflicting
"primary" systems: the numeric scale (--color-primary-50..-950, e.g.,
--color-primary-500) is blue while the semantic --primary / --color-primary is
oklch(0.205 0 0) (near-black), causing bg-primary-500 vs bg-primary to render
differently; to fix, decide which primary you want and align them by either (A)
mapping the semantic variable to the numeric scale (set --primary or
--color-primary to var(--color-primary-500) / an appropriate step) or (B) update
the numeric scale values to match the semantic primary, and if this dual-system
is intentional add a clear comment near the declarations explaining the legacy
vs shadcn distinction so consumers know when to use --color-primary-500 vs
--primary (reference variables: --color-primary-500, --color-primary, --primary
and classes/utility usages bg-primary-500, bg-primary).
src/components/products/MediaGallery.tsx (1)

36-39: Tighten selectImage's TypeScript signature.

Use SetStateAction<number> here and add an explicit : void return type instead of spelling the setter union by hand. That keeps the helper aligned with setSelectedIndex and matches the repo's TS rule.

♻️ Suggested cleanup
-import { useState } from "react";
+import { useState, type SetStateAction } from "react";
@@
-  const selectImage = (index: number | ((prev: number) => number)) => {
+  const selectImage = (index: SetStateAction<number>): void => {
     setSelectedIndex(index);
     setMainImageError(false);
   };

As per coding guidelines "Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/MediaGallery.tsx` around lines 36 - 39, Change the
selectImage signature to use React's SetStateAction for consistency with
setSelectedIndex and add an explicit void return type: replace the current
parameter type union (index: number | ((prev: number) => number)) with (index:
SetStateAction<number>) and annotate the function as (): void; keep the body
unchanged (setSelectedIndex(index); setMainImageError(false);) so it matches the
setter's type and repository TS rules.
src/components/ui/quantity-picker.tsx (1)

5-5: Use the repository import alias for shared UI primitives.

src/components/ui/quantity-picker.tsx is importing Button via ./button, but the repository guideline standardizes on @/... imports for TS/TSX files.

Proposed fix
-import { Button } from "./button";
+import { Button } from "@/components/ui/button";

As per coding guidelines, "Use absolute imports with @ alias (e.g., @/components/..., @/lib/...) instead of relative imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/quantity-picker.tsx` at line 5, Replace the relative import
of Button in quantity-picker.tsx (the import statement that references
"./button") with the repository alias import — import Button from
"@/components/ui/button" — so the component uses the standardized `@/` absolute
path for shared UI primitives; update any other relative UI imports in this file
to use the `@/` alias as well.
src/components/ui/sheet.tsx (1)

9-130: Add explicit return types to the new sheet primitives.

Sheet, SheetTrigger, SheetClose, SheetPortal, SheetOverlay, SheetContent, SheetHeader, SheetFooter, SheetTitle, and SheetDescription are all new function declarations without return annotations. Since this is a shared UI primitive module, it is worth making those return types explicit so TypeScript catches accidental non-JSX returns immediately.

💡 Example
-function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
+function Sheet(
+  { ...props }: React.ComponentProps<typeof SheetPrimitive.Root>,
+): React.JSX.Element {
   return <SheetPrimitive.Root data-slot="sheet" {...props} />;
 }

As per coding guidelines "Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sheet.tsx` around lines 9 - 130, All sheet primitive
functions lack explicit return types; update each function declaration (Sheet,
SheetTrigger, SheetClose, SheetPortal, SheetOverlay, SheetContent, SheetHeader,
SheetFooter, SheetTitle, SheetDescription) to include an explicit JSX return
type (e.g., React.ReactElement or JSX.Element) on the function signature so
TypeScript enforces correct JSX returns; ensure any cases that might return null
are typed as React.ReactElement | null and keep existing prop types (e.g.,
React.ComponentProps<typeof SheetPrimitive.Root>, etc.) unchanged.
src/components/products/filters/MobileFilterDrawer.tsx (1)

146-153: Consider full-width buttons for better mobile touch targets.

The footer action buttons lack explicit width styling. For a mobile drawer, full-width buttons (w-full) would provide larger touch targets and visual consistency with CartDrawer's footer buttons.

♻️ Suggested improvement
         {stagedCount > 0 && (
-          <Button variant="ghost" onClick={handleClearAll}>
+          <Button variant="ghost" onClick={handleClearAll} className="w-full">
             Clear all filters ({stagedCount})
           </Button>
         )}
-        <Button onClick={handleApply}>Show results</Button>
+        <Button onClick={handleApply} className="w-full">Show results</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/filters/MobileFilterDrawer.tsx` around lines 146 -
153, The footer buttons in MobileFilterDrawer (the Button elements that use
stagedCount, handleClearAll and handleApply) should be full-width to improve
mobile touch targets and match CartDrawer; update the two Button components to
include the w-full (or equivalent full-width) styling/class and ensure
spacing/stacking remains correct (e.g., vertical stack with proper gap) so both
Clear all filters ({stagedCount}) and Show results render as full-width
mobile-friendly buttons.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[country]/[locale]/(checkout)/layout.tsx:
- Around line 19-26: The header currently hard-codes Spree branding in
layout.tsx (the Link/Image block using src="/spree.png" and alt="Spree Store"),
which breaks white-labeling; update the Image to use the runtime-configured
values (e.g., storeLogo or storeLogoPath for src and storeName for the alt text,
with a sensible fallback) while preserving layout props (width/height/priority)
and keep the Link basePath logic intact so the accessible name matches the
configured storeName.
- Around line 28-33: The mobile back link becomes icon-only below `sm`; add
accessibility attributes to the Link and ArrowLeft: set an explicit aria-label
(e.g., "Back to store") on the Link component and mark the ArrowLeft icon as
decorative by adding aria-hidden="true" (or the icon library's equivalent) so
screen readers get the label instead of the raw icon; update the JSX where Link
and ArrowLeft are used to include these attributes.

In `@src/app/`[country]/[locale]/(storefront)/account/page.tsx:
- Around line 102-128: The password toggle button is absolutely positioned over
the Input, causing long text/caret to be obscured; update the Input used in this
component (the Input element with id="password", value={password},
onChange={...}) to include extra right padding so the toggle has reserved space
(e.g., add a padding-right class or inline style large enough for the Button
width), and ensure the absolute Button (the Button with onClick={() =>
setShowPassword(!showPassword)} and aria-label using showPassword) remains
positioned as-is so it overlays the reserved padding area without overlapping
the input text.

In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx:
- Around line 94-117: The password-toggle button is absolutely positioned over
the Input so caret/text can sit beneath it; update the Input(s) used for the
password fields (the Input with id="password" and the confirm-password Input) to
reserve space on the right by adding appropriate right padding (e.g., className
or style like pr-10) that matches the Button/icon width, or wrap the Input in a
container that applies padding-end; ensure the icon container (the Button using
setShowPassword and showPassword) width aligns with the padding so text/caret
never renders under the Eye/EyeOff icon.

In `@src/components/products/filters/AvailabilityDropdownContent.tsx`:
- Around line 24-48: The radio group never emits a deselect, so the
onChange(undefined) path is unreachable; add an explicit "clear" radio option
that emits an empty value and map that to undefined. Update the
DropdownMenuRadioGroup (in AvailabilityDropdownContent) to include an extra
DropdownMenuRadioItem (e.g., value "" or "none") rendered before mapping
filter.options so selecting it calls onValueChange with "" and the existing
onValueChange logic will call onChange(undefined); ensure AVAILABILITY_LABELS or
the item label covers this new value and keep isAvailabilityStatus usage intact
(or adjust isAvailabilityStatus to treat the cleared value as non-valid).

In `@src/components/products/filters/MobileFilterDrawer.tsx`:
- Around line 90-95: The SheetContent in MobileFilterDrawer is rendering a
visually hidden SheetTitle without a SheetDescription, which triggers Radix
Dialog accessibility warnings; update the SheetContent component (SheetContent)
to include aria-describedby={undefined} (same pattern used in CartDrawer) so the
dialog does not expect a missing description—apply this prop to the existing
SheetContent element enclosing SheetTitle.

In `@src/components/products/MediaGallery.tsx`:
- Around line 19-21: Clamp the gallery index against the current images prop
when deriving the displayed image instead of assuming selectedIndex is valid:
compute a safeSelectedIndex = Math.max(0, Math.min(selectedIndex, images.length
- 1)) and use that to compute selectedImage so it never becomes undefined;
additionally replace the boolean mainImageError with an error-scoped identifier
(e.g., mainImageErrorUrl) and set it in the image onError handler
(setMainImageErrorUrl(currentImage.url)) and consider the image errored only
when mainImageErrorUrl === selectedImage?.url; alternatively, if you prefer
component-level reset semantics, ensure the component is keyed by
product/variant identity so selectedIndex and error state reset when images
change.

In `@src/components/products/ProductCardSkeleton.tsx`:
- Around line 12-15: ProductCardSkeleton currently only reserves one title row
so loaded cards that clamp to two lines (see ProductCard.tsx name rendering)
cause layout shift; update ProductCardSkeleton to include a second title
placeholder above the price placeholder by adding another element matching the
existing title skeleton (e.g., another <div className="h-4 bg-gray-200 rounded
w-3/4 mb-2" /> or equivalent) so the skeleton's height matches the two-line
clamped title in ProductCard and prevents grid shift.

In `@src/components/ui/button.tsx`:
- Line 12: The default variant string in the Button variant map (the entry
currently set to "bg-primary text-primary-foreground [a]:hover:bg-primary/80")
uses the incorrect selector `[a]:hover:bg-primary/80`; replace that fragment
with `hover:bg-primary/80` so the default variant matches the hover pattern used
by the other variants (outline, secondary, ghost, destructive, link) and works
whether the Button is rendered directly or via asChild.

In `@src/components/ui/dropdown-menu.tsx`:
- Around line 45-46: The CSS selectors for Radix state in DropdownMenu
components are using incorrect data-open / data-closed attributes so open/closed
animations won't apply; update the className strings in DropdownMenuContent,
DropdownMenuSubTrigger, and DropdownMenuSubContent to replace all occurrences of
data-open: with data-[state=open]: and data-closed: with data-[state=closed]: so
the selectors match Radix's data-state="open|closed" attributes and restore the
animations and state-dependent styles.

---

Outside diff comments:
In `@next.config.ts`:
- Around line 4-44: Remove the stale SVG component type declaration that asserts
imports like `import Icon from "./icon.svg"` as React components: open the svg
type file (the `svg.d.ts` declaration that contains the `import Icon from
"./icon.svg"` / module declaration for "*.svg") and delete the entire
declaration or remove the module augmentation that exposes SVGs as React
components so the types match the removed SVGR transform; ensure no other
type-only imports remain that assert SVGs are components.

In `@src/components/search/SearchBar.tsx`:
- Around line 149-168: The combobox InputGroupInput (ref inputRef) currently
relies on placeholder text and lacks a programmatic label; add an accessible
name by providing either aria-label="Search" or aria-labelledby that points to a
visible label element for the search input, and ensure the InputGroupInput keeps
existing ARIA properties (role="combobox", aria-expanded, aria-controls,
aria-activedescendant, aria-autocomplete) so screen readers get a proper label
for the control.

---

Duplicate comments:
In `@src/components/products/VariantPicker.tsx`:
- Around line 164-167: The variant selection buttons inside VariantPicker are
missing explicit type attributes so they act as submit buttons when rendered
inside a form; update the <button> elements used for option selection (the ones
invoking handleOptionSelect(optionType.id, value) and the other similar buttons
later in the file) to include type="button" to prevent form submission and keep
them as non-submit controls.

In `@src/components/ui/quantity-picker.tsx`:
- Around line 30-49: The increment/decrement controls are regular Button
components that, when used inside a form, can act as submit buttons by default;
update both Button usages (the one with onDecrement and the one with
onIncrement) to explicitly opt out of form submission by adding type="button" to
each Button element so they behave as non-submit controls while preserving
existing props (variant, size, className, disabled, aria-label, and handlers).

---

Nitpick comments:
In `@src/app/globals.css`:
- Around line 14-24: There are two conflicting "primary" systems: the numeric
scale (--color-primary-50..-950, e.g., --color-primary-500) is blue while the
semantic --primary / --color-primary is oklch(0.205 0 0) (near-black), causing
bg-primary-500 vs bg-primary to render differently; to fix, decide which primary
you want and align them by either (A) mapping the semantic variable to the
numeric scale (set --primary or --color-primary to var(--color-primary-500) / an
appropriate step) or (B) update the numeric scale values to match the semantic
primary, and if this dual-system is intentional add a clear comment near the
declarations explaining the legacy vs shadcn distinction so consumers know when
to use --color-primary-500 vs --primary (reference variables:
--color-primary-500, --color-primary, --primary and classes/utility usages
bg-primary-500, bg-primary).

In `@src/components/products/filters/MobileFilterDrawer.tsx`:
- Around line 146-153: The footer buttons in MobileFilterDrawer (the Button
elements that use stagedCount, handleClearAll and handleApply) should be
full-width to improve mobile touch targets and match CartDrawer; update the two
Button components to include the w-full (or equivalent full-width) styling/class
and ensure spacing/stacking remains correct (e.g., vertical stack with proper
gap) so both Clear all filters ({stagedCount}) and Show results render as
full-width mobile-friendly buttons.

In `@src/components/products/MediaGallery.tsx`:
- Around line 36-39: Change the selectImage signature to use React's
SetStateAction for consistency with setSelectedIndex and add an explicit void
return type: replace the current parameter type union (index: number | ((prev:
number) => number)) with (index: SetStateAction<number>) and annotate the
function as (): void; keep the body unchanged (setSelectedIndex(index);
setMainImageError(false);) so it matches the setter's type and repository TS
rules.

In `@src/components/products/ProductGridSkeleton.tsx`:
- Line 1: The import in ProductGridSkeleton.tsx uses a relative path for
ProductCardSkeleton; change it to the repo alias form using the @ prefix (e.g.,
import { ProductCardSkeleton } from "@/components/products/ProductCardSkeleton")
so it follows the project's absolute import convention and matches other TSX
imports.

In `@src/components/ui/popover.tsx`:
- Around line 8-10: The exported component wrappers (e.g., Popover,
PopoverTrigger, PopoverContent, PopoverClose, PopoverPortal) currently rely on
inferred return types—update each function signature to include an explicit
React return type such as React.ReactElement | null (or JSX.Element | null)
while keeping the existing props types (e.g., React.ComponentProps<typeof
PopoverPrimitive.Root>); locate the functions by name (Popover, PopoverTrigger,
PopoverContent, PopoverClose, PopoverPortal) and change their declarations to
include the explicit return type to satisfy the repo TS rule.

In `@src/components/ui/quantity-picker.tsx`:
- Line 5: Replace the relative import of Button in quantity-picker.tsx (the
import statement that references "./button") with the repository alias import —
import Button from "@/components/ui/button" — so the component uses the
standardized `@/` absolute path for shared UI primitives; update any other
relative UI imports in this file to use the `@/` alias as well.

In `@src/components/ui/sheet.tsx`:
- Around line 9-130: All sheet primitive functions lack explicit return types;
update each function declaration (Sheet, SheetTrigger, SheetClose, SheetPortal,
SheetOverlay, SheetContent, SheetHeader, SheetFooter, SheetTitle,
SheetDescription) to include an explicit JSX return type (e.g.,
React.ReactElement or JSX.Element) on the function signature so TypeScript
enforces correct JSX returns; ensure any cases that might return null are typed
as React.ReactElement | null and keep existing prop types (e.g.,
React.ComponentProps<typeof SheetPrimitive.Root>, etc.) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd34fef1-4861-403a-b7e2-9965fef64cec

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd19fd and 83e8597.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (46)
  • next.config.ts
  • package.json
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(checkout)/order-placed/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/orders/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/app/[country]/[locale]/(storefront)/t/[...permalink]/page.tsx
  • src/app/[country]/[locale]/(storefront)/taxonomies/page.tsx
  • src/app/globals.css
  • src/components/cart/CartDrawer.tsx
  • src/components/checkout/AddressEditModal.tsx
  • src/components/checkout/AddressFormFields.tsx
  • src/components/checkout/CouponCode.tsx
  • src/components/checkout/OrderSummary.tsx
  • src/components/checkout/PaymentStep.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Header.tsx
  • src/components/navigation/Breadcrumbs.tsx
  • src/components/navigation/CategoryBanner.tsx
  • src/components/products/MediaGallery.tsx
  • src/components/products/ProductCard.tsx
  • src/components/products/ProductCardSkeleton.tsx
  • src/components/products/ProductCarousel.tsx
  • src/components/products/ProductGridSkeleton.tsx
  • src/components/products/VariantPicker.tsx
  • src/components/products/filters/AvailabilityDropdownContent.tsx
  • src/components/products/filters/FilterDropdown.tsx
  • src/components/products/filters/MobileFilterDrawer.tsx
  • src/components/products/filters/PriceDropdownContent.tsx
  • src/components/search/SearchBar.tsx
  • src/components/ui/button.tsx
  • src/components/ui/category-image.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/product-image.tsx
  • src/components/ui/quantity-picker.tsx
  • src/components/ui/radio-group.tsx
  • src/components/ui/sheet.tsx
  • src/components/ui/textarea.tsx
  • src/lib/utils.ts
  • src/lib/utils/color-map.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/components/products/ProductCarousel.tsx
  • src/lib/utils.ts
  • src/app/[country]/[locale]/(storefront)/products/[slug]/ProductDetails.tsx
  • src/components/ui/textarea.tsx
  • src/components/layout/Footer.tsx
  • package.json
  • src/components/products/ProductCard.tsx
  • src/components/ui/radio-group.tsx
  • src/components/layout/Header.tsx
  • src/components/ui/input-group.tsx

Comment thread src/app/[country]/[locale]/(checkout)/layout.tsx
Comment thread src/app/[country]/[locale]/(checkout)/layout.tsx
Comment thread src/app/[country]/[locale]/(storefront)/account/page.tsx
Comment thread src/app/[country]/[locale]/(storefront)/account/register/page.tsx
Comment thread src/components/products/filters/AvailabilityDropdownContent.tsx Outdated
Comment thread src/components/products/filters/MobileFilterDrawer.tsx
Comment thread src/components/products/MediaGallery.tsx Outdated
Comment thread src/components/products/ProductCardSkeleton.tsx
Comment thread src/components/ui/button.tsx Outdated
Comment thread src/components/ui/dropdown-menu.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

23-47: ⚠️ Potential issue | 🟡 Minor

Add an explicit clear option for this radio group.

DropdownMenuRadioGroup only emits one of its item values, and the current items never emit "", so the onChange(undefined) branch on Lines 25-27 is unreachable in normal use. Once a shopper selects an availability value here, they can't clear it from this menu.

💡 Proposed fix
       <DropdownMenuRadioGroup
         value={selected ?? ""}
         onValueChange={(value) => {
           if (!value || !isAvailabilityStatus(value)) {
             onChange(undefined);
           } else {
             onChange(value);
           }
         }}
       >
+        <DropdownMenuRadioItem value="">
+          Any availability
+        </DropdownMenuRadioItem>
         {filter.options.map((option) => (
           <DropdownMenuRadioItem
             key={option.id}
             value={option.id}
             onSelect={(e) => e.preventDefault()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/products/filters/AvailabilityDropdownContent.tsx` around lines
23 - 47, The radio group never emits an empty value so the onValueChange branch
that maps falsy values to onChange(undefined) is unreachable; add an explicit
"Clear" radio item inside the same DropdownMenuRadioGroup (e.g., a
DropdownMenuRadioItem with value of "" or a reserved CLEAR token) placed before
the mapped filter.options so users can clear the selection, ensure its label
(e.g., "Clear" or "All availabilities") and count if desired are shown, and keep
the existing onValueChange logic that calls onChange(undefined) when the
empty/clear value is selected; reference DropdownMenuRadioGroup,
DropdownMenuRadioItem, selected, isAvailabilityStatus, onChange, and
AVAILABILITY_LABELS to locate where to insert the new item.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/products/filters/AvailabilityDropdownContent.tsx`:
- Around line 23-47: The radio group never emits an empty value so the
onValueChange branch that maps falsy values to onChange(undefined) is
unreachable; add an explicit "Clear" radio item inside the same
DropdownMenuRadioGroup (e.g., a DropdownMenuRadioItem with value of "" or a
reserved CLEAR token) placed before the mapped filter.options so users can clear
the selection, ensure its label (e.g., "Clear" or "All availabilities") and
count if desired are shown, and keep the existing onValueChange logic that calls
onChange(undefined) when the empty/clear value is selected; reference
DropdownMenuRadioGroup, DropdownMenuRadioItem, selected, isAvailabilityStatus,
onChange, and AVAILABILITY_LABELS to locate where to insert the new item.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fbd20af-5bea-4fdf-b498-dc9713053f48

📥 Commits

Reviewing files that changed from the base of the PR and between 83e8597 and d225a11.

📒 Files selected for processing (3)
  • src/components/products/filters/AvailabilityDropdownContent.tsx
  • src/components/products/filters/OptionDropdownContent.tsx
  • src/components/products/filters/PriceDropdownContent.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/products/filters/PriceDropdownContent.tsx
  • src/components/products/filters/OptionDropdownContent.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/data/__tests__/customer.test.ts (1)

72-78: Consider adding a test case for optional fields.

The current test always includes first_name and last_name. Consider adding a test case that omits these optional fields to verify the API handles partial params correctly.

Example additional test case
it("delegates without optional name fields", async () => {
  mockRegister.mockResolvedValue(mockUser);

  const result = await register({
    email: "test@example.com",
    password: "pass",
    password_confirmation: "pass",
  });

  expect(mockRegister).toHaveBeenCalledWith({
    email: "test@example.com",
    password: "pass",
    password_confirmation: "pass",
  });
  expect(result).toBe(mockUser);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/data/__tests__/customer.test.ts` around lines 72 - 78, Add a new test
that calls register without the optional first_name and last_name to ensure
partial params are accepted: mock the register implementation (mockRegister) to
resolve to mockUser, call register with only email, password, and
password_confirmation, then assert mockRegister was called with exactly that
object and that the returned result equals mockUser; place this alongside the
existing test(s) in customer.test.ts so the suite validates both full and
minimal payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx:
- Line 17: The component uses useAuth() but only checks isAuthenticated; read
the loading flag from useAuth too and gate both the redirect logic and rendering
of the registration form until loading is false (e.g., return a
placeholder/spinner or null while loading). Update the references around
register/isAuthenticated to also destructure loading from useAuth, and ensure
any useEffect or conditional that redirects when isAuthenticated only runs after
loading === false so you avoid flashing the form or premature redirects.

---

Nitpick comments:
In `@src/lib/data/__tests__/customer.test.ts`:
- Around line 72-78: Add a new test that calls register without the optional
first_name and last_name to ensure partial params are accepted: mock the
register implementation (mockRegister) to resolve to mockUser, call register
with only email, password, and password_confirmation, then assert mockRegister
was called with exactly that object and that the returned result equals
mockUser; place this alongside the existing test(s) in customer.test.ts so the
suite validates both full and minimal payloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcd651bc-c1b4-41bb-86a8-43e4fa5b9f7b

📥 Commits

Reviewing files that changed from the base of the PR and between d225a11 and d35f9b5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/contexts/AuthContext.tsx
  • src/lib/data/__tests__/customer.test.ts
  • src/lib/data/customer.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Comment thread src/app/[country]/[locale]/(storefront)/account/register/page.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/app/[country]/[locale]/(storefront)/account/register/page.tsx (1)

128-150: ⚠️ Potential issue | 🟡 Minor

Reserve space for the password toggle inside both inputs.

The icon button is still absolutely positioned on top of the field, but neither input adds matching right padding. Text/caret can render under the toggle.

Proposed fix
               <Input
+                className="pr-10"
                 type={showPassword ? "text" : "password"}
                 id="password"
                 value={password}
@@
               <Input
+                className="pr-10"
                 type={showPasswordConfirmation ? "text" : "password"}
                 id="passwordConfirmation"
                 value={passwordConfirmation}

Also applies to: 160-186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx around
lines 128 - 150, The password visibility toggle Button is absolutely positioned
over the Input but the Input fields (password and confirm password) lack
matching right padding so text/caret can render underneath; update the two Input
usages (the one with id="password" and the confirm-password input around lines
160-186) to add sufficient right padding via their className or style (e.g., a
right padding large enough to accommodate the Button width, such as pr-10) so
the typed text and caret do not overlap the Button that toggles showPassword
(state controlled by showPassword / setShowPassword).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx:
- Around line 55-69: The registration flow in the page component calls
register(...) and sets submitting via setSubmitting(true) but does not handle
promise rejections, so setSubmitting(false) may never run; wrap the await
register(...) call in a try/catch/finally block around the existing logic (use
try to await register and check result.success/router.push(basePath +
'/account'), catch to setError with a fallback message when register throws, and
finally always call setSubmitting(false)) and ensure you reference the existing
symbols setSubmitting, register, setError, router.push and basePath so the flow
resets submitting in all failure paths.

---

Duplicate comments:
In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx:
- Around line 128-150: The password visibility toggle Button is absolutely
positioned over the Input but the Input fields (password and confirm password)
lack matching right padding so text/caret can render underneath; update the two
Input usages (the one with id="password" and the confirm-password input around
lines 160-186) to add sufficient right padding via their className or style
(e.g., a right padding large enough to accommodate the Button width, such as
pr-10) so the typed text and caret do not overlap the Button that toggles
showPassword (state controlled by showPassword / setShowPassword).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05327f39-c200-41bb-9568-c96944f033c7

📥 Commits

Reviewing files that changed from the base of the PR and between d35f9b5 and 9701621.

📒 Files selected for processing (1)
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx

Comment thread src/app/[country]/[locale]/(storefront)/account/register/page.tsx Outdated
damianlegawiec and others added 4 commits March 10, 2026 15:38
…view

- Fix Button default variant hover (was only applying to <a> elements)
- Add type="button" to VariantPicker, QuantityPicker buttons
- Add aria-describedby={undefined} to MobileFilterDrawer SheetContent
- Add aria-label to SearchBar input and checkout back link
- Add pr-10 to password inputs for toggle button clearance
- Add full-width to MobileFilterDrawer footer buttons
- Gate register page on auth loading state
- Fix register page "Sign in" link hover color
- Delete stale svg.d.ts (SVGR no longer used)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… filter reset

- Wrap register() call in try/catch/finally to prevent permanently locked
  submit button on network errors
- Clamp MediaGallery selectedIndex to valid range when images change and
  scope image error state to URL instead of sticky boolean
- Add "Any availability" option to AvailabilityDropdownContent so users
  can clear the filter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@damianlegawiec
damianlegawiec merged commit d983295 into main Mar 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants