- 
                Notifications
    You must be signed in to change notification settings 
- Fork 55
Local #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Local #11
Conversation
- Add missing zod dependency to resolve import errors - Fix homepage to use real products from database instead of hardcoded numeric IDs - Add uploaded product images from database seeding - Update package dependencies (zod, reorganized imports) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add Arabic language support with RTL layout - Fix hydration mismatch in locale handling - Update footer styling and social media links - Improve product card layout with three-line structure - Fix select component padding for dropdown arrow - Update copyright to 2025 and refine Arabic translations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
| WalkthroughThis PR introduces full internationalization with locale-prefixed routing, middleware-based locale detection/redirects, and localized UI. It adds [lang]-scoped layouts/pages, i18n config, dictionaries, and utilities. Root and auth routes now redirect to locale paths. Multiple components are refactored to accept dictionaries/lang. Fonts and global styles are updated for RTL/LTR. Changes
 Sequence Diagram(s)sequenceDiagram
  autonumber
  actor User
  participant Browser
  participant Middleware as Next.js Middleware
  participant App as App Router
  participant Dict as getDictionary
  participant Layout as [lang]/layout
  participant Page as [lang]/page|products|auth
  User->>Browser: Request /
  Browser->>Middleware: GET /
  Middleware->>Middleware: Detect locale (cookie → Accept-Language → default)
  alt Path missing locale
    Middleware-->>Browser: 302 Redirect → /{locale}/...
  else Already localized
    Middleware-->>App: Continue
  end
  Browser->>App: GET /{locale}/...
  App->>Dict: getDictionary(locale)
  Dict-->>App: dictionary
  App->>Layout: Render with {lang, dictionary}
  Layout->>Page: Render child with {lang, dictionary}
  Page-->>Browser: HTML
sequenceDiagram
  autonumber
  participant Root as (root)/page.tsx
  participant I18n as i18n.defaultLocale
  participant Nav as next/navigation.redirect
  Root->>I18n: Read defaultLocale
  Root->>Nav: redirect(`/${defaultLocale}`)
  Nav-->>Root: Throws to trigger redirect
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
 Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
 ✅ Passed checks (1 passed)
 ✨ Finishing touches
 🧪 Generate unit tests
 Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs. 
 Please see the documentation for more information. Example: reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (63)
.idea/inspectionProfiles/Project_Default.xml (2)
1-6: Don’t commit IDE-specific config; enforce linting via repo config/CI instead.This .idea profile will cause noisy diffs and cross‑IDE drift. Prefer .editorconfig + ESLint config in repo and a CI lint job; add .idea to .gitignore.
Apply to remove the file:
-<component name="InspectionProjectProfileManager"> - <profile version="1.0"> - <option name="myName" value="Project Default" /> - <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> - </profile> -</component>And add to .gitignore (repo root):
+/.idea/ +*.iml
4-4: If you keep this file, verify the inspection key and severity.Confirm the JetBrains inspection class name (“Eslint” vs “ESLint”) and consider level="ERROR" to surface lint issues prominently in IDE; ensure it doesn’t override per‑dev preferences.
.claude/settings.local.json (2)
3-11: Gate high‑risk commands behind “ask” promptsAllowing install/uninstall without confirmation can mutate lockfiles or pull unvetted packages. Move them to “ask”.
Apply:
"permissions": { "allow": [ "Bash(find:*)", - "Bash(npm run:*)", - "Bash(npm install:*)", - "Bash(npm uninstall:*)" + "Bash(npm run:*)" ], - "deny": [], - "ask": [] + "deny": [], + "ask": [ + "Bash(npm install:*)", + "Bash(npm uninstall:*)" + ] }
1-12: Consider keeping this file local-onlyIf this settings.local.json is editor/AI tooling state, add it to .gitignore to avoid repo-wide policy changes.
package.json (1)
16-49: Lock package manager to avoid install driftDeclare the repo’s package manager and version to prevent pnpm/npm mismatches seen in docs.
Example:
{ "name": "e-commerce", "version": "0.1.0", "private": true, + "packageManager": "npm@10", "scripts": {If you standardize on pnpm, set
"packageManager": "pnpm@9"and add a pnpm-lock.yaml.src/components/site/bottom-hero.tsx (4)
1-33: Localize strings and accept dictionary propsHard-coded English strings bypass the new i18n layer. Pass a dictionary and use localized labels.
Want a quick patch using Dictionary props?
23-27: Use next/image for optimization and responsive sizingSwitching from
to
improves perf and layout stability.
Apply:
+import Image from "next/image"; export default function BottomHero() { return ( <section className="relative bg-[#f5f5f5] overflow-hidden"> @@ - <div className="flex justify-center lg:justify-end"> - <img - src="/feature.png" - alt="Nike React Presto" - className="w-full max-w-lg mx-auto" - /> - </div> + <div className="flex justify-center lg:justify-end"> + <Image + src="/feature.png" + alt="Nike React Presto" + width={800} + height={600} + className="w-full max-w-lg mx-auto h-auto" + priority + /> + </div>
8-18: Heading level and CTA semanticsUse h2 (avoid multiple h1s per page) and a Link for the CTA.
- <h1 className="text-[#111111] text-3xl lg:text-4xl font-bold leading-tight"> + <h2 className="text-[#111111] text-3xl lg:text-4xl font-bold leading-tight"> @@ - <button className="bg-[#111111] text-[#ffffff] hover:bg-[#757575] px-8 py-3 rounded-full transition-colors"> - Shop Now - </button> + <a + href="/products" + className="inline-flex items-center bg-[#111111] text-[#ffffff] hover:bg-[#757575] px-8 py-3 rounded-full transition-colors" + aria-label="Shop now – browse products" + > + Shop Now + </a>
3-19: Align colors with theme tokensPrefer CSS variables/Tailwind tokens over hard-coded hex to keep dark/light themes consistent.
src/components/RiyalSymbol.tsx (1)
1-19: Improve SVG a11y and prop ergonomicsMake the icon presentational by default and accept standard SVG props; allow string sizes like "1em".
-interface RiyalSymbolProps { - className?: string; - size?: number; -} - -export default function RiyalSymbol({ className = "", size = 16 }: RiyalSymbolProps) { +import type { SVGProps } from "react"; + +interface RiyalSymbolProps extends SVGProps<SVGSVGElement> { + size?: number | string; +} + +export default function RiyalSymbol({ + className = "", + size = "1em", + ...rest +}: RiyalSymbolProps) { return ( <svg - className={`inline-block ${className}`} - width={size} - height={size} + className={`inline-block ${className}`} + width={size} + height={size} viewBox="0 0 1124.14 1256.39" fill="currentColor" + aria-hidden={rest["aria-label"] ? undefined : true} + focusable="false" + xmlns="http://www.w3.org/2000/svg" + {...rest} >src/components/SocialProviders.tsx (2)
12-19: Aria label should be dictionary-driven (Google button)Visible label is localized but aria-label isn’t; this causes AT mismatch.
- aria-label={`${variant === "sign-in" ? "Continue" : "Sign up"} with Google`} + aria-label={ + variant === "sign-in" + ? dictionary.auth.continueWithGoogle + : dictionary.auth.signUpWithGoogle + } @@ - <span>{dictionary.auth.continueWithGoogle}</span> + <span> + {variant === "sign-in" + ? dictionary.auth.continueWithGoogle + : dictionary.auth.signUpWithGoogle} + </span>
20-27: Match Apple visible label to variantKeep visual label consistent with aria-label for sign-up flows.
- <span>{dictionary.auth.continueWithApple}</span> + <span> + {variant === "sign-in" + ? dictionary.auth.continueWithApple + : dictionary.auth.signUpWithApple} + </span>CLAUDE.md (6)
44-52: Add fenced code language for the file treeSpecify a language to satisfy markdownlint (use text).
-``` +```text components/internationalization/ ├── config.ts # Configuration and types ├── middleware.ts # Locale detection logic ├── dictionaries.ts # Dictionary loading ├── use-locale.ts # URL switching utility -├── en.json # English transliation -└── ar.json # Arabic transliation +├── en.json # English translation +└── ar.json # Arabic translation--- `56-58`: **Add language to the “Data Flow” diagram block** Label as text. ```diff -``` +```text Request → Middleware → Locale Detection → Dictionary Loading → Props → Components--- `88-106`: **Add language to the “Feature-Based File Structure” block** Label as text for markdownlint compliance. ```diff -``` +```text src/ ├── middleware.ts # Root middleware orchestrator ├── app/ │ ├── [lang]/ # Dynamic locale routing │ │ ├── layout.tsx # Locale-aware layout │ │ ├── page.tsx # Localized pages │ │ └── (routes)/ # All your routes │ └── globals.css ├── components/ │ └── internationalization/ # 🎯 All i18n logic here │ ├── config.ts # Configuration & types │ ├── middleware.ts # Middleware logic │ ├── dictionaries.ts # Dictionary loader │ ├── use-locale.ts # URL switching hook │ ├── en.json # English translations │ └── ar.json # Arabic translations--- `388-456`: **Import path inconsistency: i18n-config vs config** Earlier sections define config.ts; here imports use i18n-config. Standardize on one name. ```diff -import { type Locale, localeConfig } from "@/components/internationalization/i18n-config"; +import { type Locale, localeConfig } from "@/components/internationalization/config";Repeat similarly in other snippets that import from i18n-config.
8-20: Use headings instead of bold for section titles“Feature-Based i18n Architecture for New & Existing Projects” should be a heading, not bold, to satisfy MD036.
-**Feature-Based i18n Architecture for New & Existing Projects** +## Feature-Based i18n Architecture for New & Existing Projects
70-83: Package manager consistencyDocs mix npm and pnpm. Pick one and note it at the top, or provide commands for both.
src/components/internationalization/README.md (9)
1-3: Heading formatting (MD036)Use a heading, not bold emphasis, for the title.
-# Internationalization - -**Feature-Based i18n Architecture for New & Existing Projects** +# Internationalization + +## Feature-Based i18n Architecture for New & Existing Projects
44-52: Add language to fenced code blockLabel the file tree block as text; also fix “transliation” typos.
-``` +```text components/internationalization/ @@ -├── en.json # English transliation -└── ar.json # Arabic transliation +├── en.json # English translation +└── ar.json # Arabic translation--- `56-58`: **Add language to the “Data Flow” block** Label as text. ```diff -``` +```text Request → Middleware → Locale Detection → Dictionary Loading → Props → Components--- `118-153`: **Config filename consistency across docs** You create config.ts but later import from i18n-config. Keep a single name. ```diff -export type Locale = (typeof i18n)['locales'][number]; +export type Locale = (typeof i18n)['locales'][number];Action: replace later imports of i18n-config with config.
374-381: Package manager consistencyThis guide uses pnpm while CLAUDE.md uses npm. Add a note or dual commands to avoid confusion.
388-456: Fix import path in layout snippetMatches earlier suggestion: import from config, not i18n-config.
-import { type Locale, localeConfig } from "@/components/internationalization/i18n-config"; +import { type Locale, localeConfig } from "@/components/internationalization/config";
462-500: Type import path alignmentSame issue in page.tsx snippet; update to config.
-import type { Locale } from '@/components/internationalization/i18n-config'; +import type { Locale } from '@/components/internationalization/config';
631-668: Language Switcher snippet: fix import pathStandardize on config.
-import { i18n, localeConfig, type Locale } from '@/components/internationalization/i18n-config'; +import { i18n, localeConfig, type Locale } from '@/components/internationalization/config';
789-846: Client component snippet: add language to code fenceAdd typescript language to satisfy markdownlint MD040.
-``` +```typescript // ✅ CORRECT: Server components use getDictionaryApply similarly to other unlabeled code fences.
src/app/layout.tsx (1)
28-31: Set default lang/dir on for SSR a11y/SEO; let LocaleProvider adjust post-hydration.Adds better semantics on first paint while keeping your client-side override.
Apply this diff:
- <html suppressHydrationWarning> + <html lang="en" dir="ltr" suppressHydrationWarning>src/components/LanguageSwitcher.tsx (1)
13-16: Avoid false positives when hiding on auth routes—match path segments, not substring.Prevents hiding on unrelated paths like “/en/sign-incentives”.
Apply this diff:
- // Don't show switcher on auth pages - if (pathname.includes('/sign-in') || pathname.includes('/sign-up')) { + // Don't show switcher on auth pages + const pathNoQuery = pathname.split('?')[0].split('#')[0]; + const segments = pathNoQuery.split('/'); + if (segments.includes('sign-in') || segments.includes('sign-up')) { return null; }src/middleware.ts (1)
1-1: Use a type-only import for NextRequest.Saves a runtime import in the edge bundle.
Apply this diff:
-import { NextRequest } from 'next/server'; +import type { NextRequest } from 'next/server';src/app/[lang]/page.tsx (2)
1-1: Drop unnecessary React importNext.js App Router doesn’t require React import in TSX files.
-import React from "react";
41-53: Optional: handle empty product list gracefullyIf no products, render a localized empty state instead of an empty grid.
src/components/LocaleProvider.tsx (1)
12-31: Prevent initial LTR/RTL flash (optional)Set lang/dir on the server in layout.html/head to avoid post-hydration flips; keep this effect as a safety net.
Confirm src/app/[lang]/layout.tsx sets htmlAttributes or equivalent to pre-render correct dir.
src/components/SizePicker.tsx (1)
25-41: Optional a11y: reflect single-select intentConsider role="radiogroup" on the container and role="radio" + aria-checked on buttons for screen readers.
src/components/internationalization/dictionaries.ts (1)
10-17: Type dictionary shape for better safetyDictionary currently resolves to any; type it from en.json for compile-time coverage.
-import type { Locale } from "./config"; +import type { Locale } from "./config"; +import type en from "./en.json"; @@ -export const getDictionary = async (locale: Locale) => { +export type Dictionary = typeof en; + +export const getDictionary = async (locale: Locale): Promise<Dictionary> => { @@ -// Type helper for component props -export type Dictionary = Awaited<ReturnType<typeof getDictionary>>; +// Component prop helper remains DictionaryAlso applies to: 19-20
src/components/internationalization/en.json (2)
66-67: Inconsistent “colour” vs “color” keys.product.colour uses British spelling while filters.color uses American. This invites accidental mismatches in lookups.
Pick one spelling repo‑wide; if “color” is standard, rename product.colour to product.color in all locales and call sites.
Also applies to: 84-90
212-218: Avoid numeric JSON keys for error codes."404" and "500" force bracket notation and are error‑prone for typings.
Prefer semantically named keys, e.g.:
- "404": "Page Not Found", - "404Description": "The page you're looking for doesn't exist.", + "notFound": "Page Not Found", + "notFoundDescription": "The page you're looking for doesn't exist.", - "500": "Internal Server Error", - "500Description": "Something went wrong on our end.", + "internalError": "Internal Server Error", + "internalErrorDescription": "Something went wrong on our end.",src/components/internationalization/middleware.ts (2)
46-50: Set cookie path to root to ensure it’s sent on all routes.Without path, some runtimes may default to the current path.
response.cookies.set('NEXT_LOCALE', locale, { maxAge: 365 * 24 * 60 * 60, // 1 year sameSite: 'lax', secure: process.env.NODE_ENV === 'production', + path: '/', });
9-11: Narrow the cookieLocale type instead of using any.Use the Locale type for safer includes checks.
-import { i18n } from './config'; +import { i18n, type Locale } from './config'; ... - if (cookieLocale && i18n.locales.includes(cookieLocale as any)) { - return cookieLocale; + if (cookieLocale && i18n.locales.includes(cookieLocale as Locale)) { + return cookieLocale as Locale; }src/components/Sort.tsx (1)
13-18: Stabilize OPTIONS with useMemo to avoid rebuilding on every render.Not critical, but avoids re-allocations on state changes.
-export default function Sort({ dictionary }: SortProps) { - const OPTIONS = [ +export default function Sort({ dictionary }: SortProps) { + const OPTIONS = useMemo(() => ([ { label: dictionary.sort.featured, value: "featured" }, { label: dictionary.sort.newest, value: "newest" }, { label: dictionary.sort.priceHighToLow, value: "price_desc" }, { label: dictionary.sort.priceLowToHigh, value: "price_asc" }, - ] as const; + ] as const), [dictionary]);src/components/internationalization/config.ts (1)
8-28: Tie localeConfig to Locale keys at compile time.Use satisfies to guarantee config keys stay in sync with i18n.locales.
-export const localeConfig = { +export const localeConfig = { 'en': { name: 'English', nativeName: 'English', dir: 'ltr', flag: '🇺🇸', dateFormat: 'MM/dd/yyyy', currency: 'SAR', // Using SAR for Saudi market currencySymbol: 'SAR', }, 'ar': { name: 'Arabic', nativeName: 'العربية', dir: 'rtl', flag: '🇸🇦', dateFormat: 'dd/MM/yyyy', currency: 'SAR', currencySymbol: 'ر.س', }, -} as const; +} as const satisfies Record<Locale, { + name: string; + nativeName: string; + dir: 'ltr' | 'rtl'; + flag: string; + dateFormat: string; + currency: string; + currencySymbol: string; +}>;src/components/site/trend.tsx (3)
29-31: Use dictionary for the button label.Leverage home.shopNow so both locales render from translations.
- <button className="bg-[#ffffff] text-[#111111] hover:bg-[#f0f0f0] px-6 py-2 rounded-full transition-colors"> - {dictionary ? (isRTL ? "تسوق الآن" : "Shop Now") : "Shop Now"} + <button className="bg-[#ffffff] text-[#111111] hover:bg-[#f0f0f0] px-6 py-2 rounded-full transition-colors"> + {dictionary ? dictionary.home.shopNow : "Shop Now"} </button>
1-11: Reuse centralized RTL utility instead of ad‑hoc check.Prefer isRTL(lang) from config for consistency.
-import { type Locale } from "@/components/internationalization/config"; +import { type Locale, isRTL } from "@/components/internationalization/config"; ... -export default function Trend({ dictionary, lang }: TrendProps) { - const isRTL = lang === 'ar'; +export default function Trend({ dictionary, lang }: TrendProps) { + const isRTL = !!(lang && isRTL(lang));
22-23: Consider next/image for performance (LCP/CWV).The three
tags bypass Next’s image optimization. If these are in /public, switching to next/image yields better loading and responsive behavior.
Would you like a follow‑up diff converting these to
with appropriate sizes?
Also applies to: 39-43, 54-57
src/app/(root)/products/[id]/page.tsx (1)
4-11: Unnecessary async/await on params; fix typing.Next App Router passes params synchronously. The current type Promise<{id:string}> works by accident but is misleading.
-export default async function ProductDetailRootPage({ - params -}: { - params: Promise<{ id: string }> -}) { - const { id } = await params; +export default function ProductDetailRootPage({ + params, +}: { + params: { id: string } +}) { + const { id } = params; redirect(`/${i18n.defaultLocale}/products/${id}`); }src/components/site/hero.tsx (2)
9-11: Use shared RTL helper.Mirror Trend: compute RTL via isRTL(lang) for consistency.
-import { type Locale } from "@/components/internationalization/config"; +import { type Locale, isRTL as computeRTL } from "@/components/internationalization/config"; ... -export default function Hero({ dictionary, lang }: HeroProps) { - const isRTL = lang === 'ar'; +export default function Hero({ dictionary, lang }: HeroProps) { + const isRTL = !!(lang && computeRTL(lang));
31-35: Prefer next/image for the shoe visual (LCP).Switching to
with priority improves performance and avoids layout shifts.
- <img - src="/hero-shoe.png" - alt="Nike Air Jordan Sneaker" - className="w-full max-w-lg h-auto" - /> + <Image + src="/hero-shoe.png" + alt="Nike Air Jordan Sneaker" + width={800} + height={800} + priority + className="w-full max-w-lg h-auto" + />src/components/Card.tsx (1)
45-50: Unify numeric price formatting with locale/currency.Current rendering shows a static symbol + fixed(2). Use Intl.NumberFormat with currency from localeConfig to handle separators and locale numerals.
-import RiyalSymbol from "./RiyalSymbol"; +import RiyalSymbol from "./RiyalSymbol"; +import { localeConfig, type Locale as Lang } from "@/components/internationalization/config"; +import { useMemo } from "react"; ... - const displayPrice = - price === undefined ? undefined : typeof price === "number" ? ( + const formatter = useMemo(() => { + const l = (lang ?? 'en') as Lang; + const currency = localeConfig[l].currency; + const locale = l === 'ar' ? 'ar-SA' : 'en-US'; + return new Intl.NumberFormat(locale, { style: 'currency', currency }); + }, [lang]); + + const displayPrice = + price === undefined ? undefined : typeof price === "number" ? ( <span className="flex items-center gap-1"> - <RiyalSymbol size={14} /> - {price.toFixed(2)} + <RiyalSymbol size={14} /> + {formatter.format(price).replace(/^SAR\s*/i, "")} </span> ) : price;Note: stripping “SAR ” keeps your RiyalSymbol while still gaining proper grouping/locale numerals.
src/components/internationalization/use-locale.ts (1)
10-24: Potential edge case with locale replacement inuseSwitchLocaleHrefThe current implementation uses
pathname.replace()which could incorrectly replace locale strings that appear elsewhere in the path.For example, if the path is
/en/products/women-shoes, switching toarwould work correctly. However, if you have a path like/en/arena/productsand try to switch fromentoar, it might incorrectly produce/arena/productsinstead of/ar/arena/productsbecause theenin "arena" gets replaced.Consider using a more precise replacement:
- const newPathname = pathname.replace(`/${currentLocale}`, `/${targetLocale}`); + const newPathname = pathname.replace( + new RegExp(`^/${currentLocale}(/|$)`), + `/${targetLocale}$1` + );src/app/[lang]/layout.tsx (2)
18-20: Consider using standardContent-LanguageHTTP headerThe metadata uses a non-standard
accept-languagein theotherfield. For proper HTTP semantics and SEO, consider using the standard approach.Next.js metadata API doesn't directly support
Content-Language, but you can set it via middleware or use the standardlocaleproperty:other: { - 'accept-language': lang, }, + // Consider setting this in middleware.ts instead for proper HTTP headers
52-54: Type assertion concern with locale keysUsing
Object.keys(localeConfig)returnsstring[], notLocale[]. While this works at runtime, it bypasses TypeScript's type safety.Consider a type-safe approach:
-export function generateStaticParams() { - return Object.keys(localeConfig).map((lang) => ({ lang })); -} +export function generateStaticParams() { + return (Object.keys(localeConfig) as Locale[]).map((lang) => ({ lang })); +}Or use a more explicit approach:
+import { i18n } from "@/components/internationalization/config"; + export function generateStaticParams() { - return Object.keys(localeConfig).map((lang) => ({ lang })); + return i18n.locales.map((lang) => ({ lang })); }src/components/AuthForm.tsx (2)
29-29: Add space afterifstatementMinor formatting: missing space after
ifkeyword.- if(result?.ok) router.push(`/${lang}`); + if (result?.ok) router.push(`/${lang}`);
135-141: Internal links should use Next.js Link componentThe Terms of Service and Privacy Policy links use anchor tags (
<a>) instead of Next.jsLinkcomponent, which bypasses client-side navigation.- <a href={`/${lang}`} className="underline"> + <Link href={`/${lang}`} className="underline"> {dictionary.auth.termsOfService} - </a>{" "} + </Link>{" "} {dictionary.auth.and}{" "} - <a href={`/${lang}`} className="underline"> + <Link href={`/${lang}`} className="underline"> {dictionary.auth.privacyPolicy} - </a> + </Link>src/components/Navbar.tsx (2)
55-69: Search and cart buttons lack functionalityThe search and shopping cart buttons are currently non-functional placeholders. While this may be intentional for the current PR scope, these should be connected to actual functionality.
These buttons should trigger actual search and cart functionality. Would you like me to create GitHub issues to track the implementation of:
- Search functionality with a search overlay/modal
- Shopping cart state management and UI
104-114: Mobile view action buttons need onClick handlersThe mobile search and cart buttons have ARIA labels but no click handlers, making them non-functional.
If these are meant to be functional (matching desktop behavior), add onClick handlers. If they're placeholders, consider adding a TODO comment to track implementation:
- <button className="flex items-center gap-2 text-body" aria-label={dictionary.navigation.search}> + <button + className="flex items-center gap-2 text-body" + aria-label={dictionary.navigation.search} + onClick={() => {/* TODO: Implement search */}} + >src/components/Footer.tsx (4)
56-56: Alt text likely incorrect brandLogo alt says “Nike”. Use a neutral/brand-accurate label to avoid misleading a11y and SEO.
Apply this diff:
- <Image src="/logo.svg" alt="Nike" width={48} height={48} /> + <Image src="/logo.svg" alt="Logo" width={48} height={48} />
59-77: All footer links route to the locale rootEvery item links to
/${lang}(also legal links). This harms navigation and SEO. Prefer deep links (e.g., category/guide/terms pages) or add hrefs to the dictionary.Example refactor using label+href pairs (illustrative):
- const columns = [ - { title: ..., links: [label1, label2, ...] }, - ... - ] as const; + const columns = [ + { + title: dictionary.footer.featured.title, + links: [ + { label: dictionary.footer.featured.airForce1, href: `/${lang}/products?search=air%20force%201` }, + // ... + ], + }, + // ... + ] as const;- {col.links.map((l) => ( - <li key={l}> - <Link - href={`/${lang}`} + {col.links.map((l) => ( + <li key={typeof l === 'string' ? l : l.label}> + <Link + href={typeof l === 'string' ? `/${lang}` : l.href} className="text-caption text-light-400 hover:text-light-300" > - {l} + {typeof l === 'string' ? l : l.label} </Link>And for legal:
- <Link href={`/${lang}`}>{t}</Link> + <Link href={`/${lang}/legal`}>{t}</Link>If deep routes aren’t ready yet, confirm that linking to placeholder pages is intentional.
Also applies to: 107-116
81-94: WhatsApp wa.me format may be invalidwa.me expects E.164 digits without a leading 00. Recommend removing the 00 prefix.
- { icon: MessageCircle, alt: "WhatsApp", link: "https://wa.me/00966557721603" }, + { icon: MessageCircle, alt: "WhatsApp", link: "https://wa.me/966557721603" },Please click-test on a device to ensure it opens WhatsApp correctly.
65-71: Potential duplicate React keysUsing the link label as a key can collide across locales/columns. Safer to prefix with the column or use an index.
- <li key={l}> + <li key={`${col.title}:${l}`}>src/app/[lang]/products/page.tsx (2)
24-37: Badge generation: handle empty values and support priceMin/priceMaxCurrent logic can throw on empty query items (e.g.,
gender[]=) and ignorespriceMin/priceMax. Use normalizedparsedfilters and guard empties.- const activeBadges: string[] = []; - (sp.gender ? (Array.isArray(sp.gender) ? sp.gender : [sp.gender]) : []).forEach((g) => - activeBadges.push(String(g)[0].toUpperCase() + String(g).slice(1)) - ); - (sp.size ? (Array.isArray(sp.size) ? sp.size : [sp.size]) : []).forEach((s) => activeBadges.push(`${dictionary.filters.size}: ${s}`)); - (sp.color ? (Array.isArray(sp.color) ? sp.color : [sp.color]) : []).forEach((c) => - activeBadges.push(String(c)[0].toUpperCase() + String(c).slice(1)) - ); - (sp.price ? (Array.isArray(sp.price) ? sp.price : [sp.price]) : []).forEach((p) => { - const [min, max] = String(p).split("-"); - const label = min && max ? `${min} - ${max} ${dictionary.product.currency}` : min && !max ? `${dictionary.filters.over} ${min} ${dictionary.product.currency}` : `0 - ${max} ${dictionary.product.currency}`; - activeBadges.push(label); - }); + const activeBadges: string[] = []; + const cap = (s: string) => (s ? s[0].toUpperCase() + s.slice(1) : s); + parsed.genderSlugs.forEach((g) => g && activeBadges.push(cap(g))); + parsed.sizeSlugs.forEach((s) => s && activeBadges.push(`${dictionary.filters.size}: ${s}`)); + parsed.colorSlugs.forEach((c) => c && activeBadges.push(cap(c))); + parsed.priceRanges.forEach(([min, max]) => { + const label = + min != null && max != null + ? `${min} - ${max} ${dictionary.product.currency}` + : min != null + ? `${dictionary.filters.over} ${min} ${dictionary.product.currency}` + : `0 - ${max ?? 0} ${dictionary.product.currency}`; + activeBadges.push(label); + }); + if (parsed.priceMin != null || parsed.priceMax != null) { + const min = parsed.priceMin ?? 0; + const max = parsed.priceMax; + activeBadges.push( + max != null ? `${min} - ${max} ${dictionary.product.currency}` : `${dictionary.filters.over} ${min} ${dictionary.product.currency}` + ); + }
58-60: Filters may need lang for link generationIf Filters builds URLs, pass
langto ensure locale-scoped links. If not required, ignore.- <Filters dictionary={dictionary} /> + <Filters dictionary={dictionary} lang={lang} />src/app/[lang]/products/[id]/page.tsx (2)
22-37: Type safety: avoidanyfor dictionary and add lang to reviews for proper date localeUse the
Dictionarytype and passlangintoReviewsSectionso dates render in the selected locale.-import { getDictionary } from "@/components/internationalization/dictionaries"; +import { getDictionary, type Dictionary } from "@/components/internationalization/dictionaries";-function NotFoundBlock({ dictionary, lang }: { dictionary: any; lang: Locale }) { +function NotFoundBlock({ dictionary, lang }: { dictionary: Dictionary; lang: Locale }) {-async function ReviewsSection({ productId, dictionary }: { productId: string; dictionary: any }) { +async function ReviewsSection({ productId, dictionary, lang }: { productId: string; dictionary: Dictionary; lang: Locale }) {- <p className="mt-2 text-caption text-dark-700">{new Date(r.createdAt).toLocaleDateString()}</p> + <p className="mt-2 text-caption text-dark-700">{new Date(r.createdAt).toLocaleDateString(lang)}</p>-async function AlsoLikeSection({ productId, dictionary, lang }: { productId: string; dictionary: any; lang: Locale }) { +async function AlsoLikeSection({ productId, dictionary, lang }: { productId: string; dictionary: Dictionary; lang: Locale }) {- <ReviewsSection productId={product.id} dictionary={dictionary} /> + <ReviewsSection productId={product.id} dictionary={dictionary} lang={lang} />Also applies to: 39-79, 81-102
182-194: Avoid rendering an empty price element
formatPrice(displayPrice)can beundefined, yielding an empty<p>. Guard rendering for cleaner DOM.- <div className="flex items-center gap-3"> - <p className="text-lead text-dark-900">{formatPrice(displayPrice)}</p> + <div className="flex items-center gap-3"> + {displayPrice != null && ( + <p className="text-lead text-dark-900">{formatPrice(displayPrice)}</p> + )} {compareAt && (
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (10)
- package-lock.jsonis excluded by- !**/package-lock.json
- pnpm-lock.yamlis excluded by- !**/pnpm-lock.yaml
- public/riyal.svgis excluded by- !**/*.svg
- public/site/bottom-hero.pngis excluded by- !**/*.png
- public/site/hero.pngis excluded by- !**/*.png
- public/site/trend-three.pngis excluded by- !**/*.png
- public/site/trend-two.pngis excluded by- !**/*.png
- public/site/trend.pngis excluded by- !**/*.png
- static/uploads/shoes/33e0cc6f-555e-465f-b3f3-441b7b7864b3-shoe-1.jpgis excluded by- !**/*.jpg
- static/uploads/shoes/bb937b63-189e-4acd-b3c0-ce5469fc03a1-shoe-1.jpgis excluded by- !**/*.jpg
📒 Files selected for processing (42)
- .claude/settings.local.json(1 hunks)
- .idea/inspectionProfiles/Project_Default.xml(1 hunks)
- .idea/vcs.xml(1 hunks)
- CLAUDE.md(1 hunks)
- package.json(1 hunks)
- src/app/(auth)/sign-in/page.tsx(1 hunks)
- src/app/(auth)/sign-up/page.tsx(1 hunks)
- src/app/(root)/layout.tsx(1 hunks)
- src/app/(root)/page.tsx(1 hunks)
- src/app/(root)/products/[id]/page.tsx(1 hunks)
- src/app/(root)/products/page.tsx(1 hunks)
- src/app/[lang]/layout.tsx(1 hunks)
- src/app/[lang]/page.tsx(1 hunks)
- src/app/[lang]/products/[id]/page.tsx(1 hunks)
- src/app/[lang]/products/page.tsx(1 hunks)
- src/app/[lang]/sign-in/page.tsx(1 hunks)
- src/app/[lang]/sign-up/page.tsx(1 hunks)
- src/app/globals.css(1 hunks)
- src/app/layout.tsx(2 hunks)
- src/components/AuthForm.tsx(8 hunks)
- src/components/Card.tsx(3 hunks)
- src/components/Filters.tsx(6 hunks)
- src/components/Footer.tsx(3 hunks)
- src/components/LanguageSwitcher.tsx(1 hunks)
- src/components/LocaleProvider.tsx(1 hunks)
- src/components/Navbar.tsx(4 hunks)
- src/components/RiyalSymbol.tsx(1 hunks)
- src/components/SizePicker.tsx(1 hunks)
- src/components/SocialProviders.tsx(2 hunks)
- src/components/Sort.tsx(2 hunks)
- src/components/index.ts(1 hunks)
- src/components/internationalization/README.md(1 hunks)
- src/components/internationalization/ar.json(1 hunks)
- src/components/internationalization/config.ts(1 hunks)
- src/components/internationalization/dictionaries.ts(1 hunks)
- src/components/internationalization/en.json(1 hunks)
- src/components/internationalization/middleware.ts(1 hunks)
- src/components/internationalization/use-locale.ts(1 hunks)
- src/components/site/bottom-hero.tsx(1 hunks)
- src/components/site/hero.tsx(1 hunks)
- src/components/site/trend.tsx(1 hunks)
- src/middleware.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (27)
src/app/(auth)/sign-in/page.tsx (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/components/LanguageSwitcher.tsx (2)
src/components/internationalization/config.ts (1)
Locale(6-6)src/components/internationalization/use-locale.ts (1)
useSwitchLocaleHref(7-25)
src/app/[lang]/page.tsx (5)
src/components/internationalization/config.ts (1)
Locale(6-6)src/lib/auth/actions.ts (1)
getCurrentUser(108-119)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/lib/actions/product.ts (1)
getAllProducts(45-210)src/components/Card.tsx (1)
Card(30-99)
src/app/[lang]/products/page.tsx (6)
src/components/internationalization/config.ts (1)
Locale(6-6)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/lib/utils/query.ts (1)
parseFilterParams(96-158)src/lib/actions/product.ts (1)
getAllProducts(45-210)src/components/RiyalSymbol.tsx (1)
RiyalSymbol(6-19)src/components/Card.tsx (1)
Card(30-99)
src/middleware.ts (1)
src/components/internationalization/middleware.ts (1)
localizationMiddleware(25-53)
src/components/internationalization/middleware.ts (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/components/internationalization/use-locale.ts (1)
src/components/internationalization/config.ts (3)
Locale(6-6)
i18n(1-4)
localeConfig(9-28)
src/app/(root)/page.tsx (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/app/[lang]/sign-up/page.tsx (4)
src/components/internationalization/config.ts (1)
Locale(6-6)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/components/AuthForm.tsx (1)
AuthForm(17-147)src/lib/auth/actions.ts (1)
signUp(63-82)
src/app/[lang]/sign-in/page.tsx (4)
src/components/internationalization/config.ts (1)
Locale(6-6)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/components/AuthForm.tsx (1)
AuthForm(17-147)src/lib/auth/actions.ts (1)
signIn(89-106)
src/app/(root)/products/[id]/page.tsx (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/components/SizePicker.tsx (1)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)
src/components/internationalization/dictionaries.ts (1)
src/components/internationalization/config.ts (1)
Locale(6-6)
src/app/(auth)/sign-up/page.tsx (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/components/site/trend.tsx (2)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (2)
Locale(6-6)
isRTL(30-32)
src/app/[lang]/products/[id]/page.tsx (9)
src/components/RiyalSymbol.tsx (1)
RiyalSymbol(6-19)src/components/internationalization/config.ts (1)
Locale(6-6)src/lib/actions/product.ts (5)
Review(385-392)
getProductReviews(401-425)
RecommendedProduct(394-399)
getRecommendedProducts(427-500)
getProduct(227-384)src/components/CollapsibleSection.tsx (1)
CollapsibleSection(14-46)src/components/Card.tsx (1)
Card(30-99)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/components/ProductGallery.tsx (1)
ProductGallery(24-124)src/components/ColorSwatches.tsx (1)
ColorSwatches(19-51)src/components/SizePicker.tsx (1)
SizePicker(13-44)
src/app/[lang]/layout.tsx (5)
src/components/internationalization/config.ts (2)
Locale(6-6)
localeConfig(9-28)src/components/internationalization/dictionaries.ts (1)
getDictionary(10-17)src/components/LocaleProvider.tsx (1)
LocaleProvider(7-34)src/components/Navbar.tsx (1)
Navbar(16-123)src/components/Footer.tsx (1)
Footer(12-123)
src/components/SocialProviders.tsx (1)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)
src/components/site/hero.tsx (2)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (2)
Locale(6-6)
isRTL(30-32)
src/components/Card.tsx (3)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (1)
Locale(6-6)src/components/RiyalSymbol.tsx (1)
RiyalSymbol(6-19)
src/app/(root)/products/page.tsx (1)
src/components/internationalization/config.ts (1)
i18n(1-4)
src/components/Filters.tsx (2)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/lib/utils/query.ts (1)
getArrayParam(65-71)
src/components/Sort.tsx (1)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)
src/components/AuthForm.tsx (3)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (1)
Locale(6-6)src/components/SocialProviders.tsx (1)
SocialProviders(9-30)
src/components/LocaleProvider.tsx (2)
src/components/internationalization/config.ts (3)
Locale(6-6)
localeConfig(9-28)
isRTL(30-32)src/middleware.ts (1)
config(5-7)
src/components/Footer.tsx (2)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (1)
Locale(6-6)
src/components/Navbar.tsx (3)
src/components/internationalization/dictionaries.ts (1)
Dictionary(20-20)src/components/internationalization/config.ts (1)
Locale(6-6)src/components/LanguageSwitcher.tsx (1)
LanguageSwitcher(9-30)
🪛 markdownlint-cli2 (0.17.2)
src/components/internationalization/README.md
3-3: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
44-44: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
56-56: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
88-88: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (26)
src/app/globals.css (1)
60-68: LGTM: font utilities align with next/font variable setupClasses map cleanly to --font-inter and --font-rubik.
src/components/SocialProviders.tsx (1)
1-27: Verified — translation keys existauth.continueWithGoogle, auth.signUpWithGoogle, auth.continueWithApple, and auth.signUpWithApple exist in src/components/internationalization/en.json and src/components/internationalization/ar.json (lines 45–48).
package.json (1)
16-35: Compatibility confirmed — Next.js 15.4.x + React 19.1.0
Next.js 15.4.x officially supports React 19.x; react@19.1.0 with next@15.4.6 is supported and safe to merge.src/components/internationalization/README.md (1)
270-284: ```shell
#!/bin/bash
set -euo pipefailecho "PWD: $(pwd)"
if [ -f src/middleware.ts ]; then
echo
echo "=== src/middleware.ts ==="
sed -n '1,240p' src/middleware.ts || true
else
echo
echo "src/middleware.ts not found"
fiecho
echo "=== Searching repo for matcher/config/_next/static/file-extension patterns ==="
rg -n --hidden --no-ignore -S "export const config" || true
rg -n --hidden --no-ignore -S "matcher:" || true
rg -n --hidden --no-ignore -S "_next/static" || true
rg -n --hidden --no-ignore -F ".\.." || true
rg -n --hidden --no-ignore -F ".*\.[a-zA-Z0-9]+$" || trueif [ -f src/components/internationalization/README.md ]; then
echo
echo "=== README snippet (lines 270-284) ==="
sed -n '270,284p' src/components/internationalization/README.md || true
fiecho
echo "Script completed"</blockquote></details> <details> <summary>src/components/index.ts (1)</summary><blockquote> `9-10`: **Barrel re-exports look good.** Centralized access via the components index is tidy and non-breaking. </blockquote></details> <details> <summary>src/app/(root)/layout.tsx (1)</summary><blockquote> `5-6`: **Passthrough root-group layout is appropriate for redirect-only routes.** No wrappers means no duplicate UI before locale redirect. </blockquote></details> <details> <summary>src/components/LanguageSwitcher.tsx (1)</summary><blockquote> `21-28`: **LGTM on toggle/link behavior.** Target-locale derivation and href calculation are straightforward and correct. </blockquote></details> <details> <summary>src/app/(auth)/sign-up/page.tsx (1)</summary><blockquote> `5-6`: **Redirect approach is clean and locale-aware.** Keeps legacy route functional while consolidating to localized pages. </blockquote></details> <details> <summary>src/app/[lang]/sign-in/page.tsx (1)</summary><blockquote> `6-13`: **Server action present — no change required.** src/lib/auth/actions.ts contains "use server" on line 1, so signIn is a server action and safe to call from AuthForm.onSubmit. </blockquote></details> <details> <summary>src/app/[lang]/sign-up/page.tsx (1)</summary><blockquote> `6-13`: **Resolved — signUp is a Server Action** signUp is exported from src/lib/auth/actions.ts, and that file begins with "use server", so passing it into the client-side AuthForm as a server action is valid. </blockquote></details> <details> <summary>src/app/(auth)/sign-in/page.tsx (1)</summary><blockquote> `4-6`: **Server redirect to default-locale sign-in — LGTM** Behavior is correct and consistent with locale-prefixed routing. Please confirm src/app/[lang]/sign-in/page.tsx exists to avoid 404s after redirect. </blockquote></details> <details> <summary>src/app/[lang]/page.tsx (1)</summary><blockquote> `47-49`: **Verify fallback image asset exists** Ensure public/shoes/shoe-1.jpg ships; otherwise Next/Image will error at runtime. </blockquote></details> <details> <summary>src/components/internationalization/ar.json (1)</summary><blockquote> `1-219`: **Arabic translations file — LGTM** Structure and keys look consistent; values read well for RTL contexts. Run a quick key-diff against en.json to ensure no missing/extra keys. </blockquote></details> <details> <summary>src/app/(root)/page.tsx (1)</summary><blockquote> `4-6`: **Root redirect to default locale — LGTM** Straightforward and avoids rendering at /. Ensure middleware doesn’t also redirect / → /{locale} to avoid double hops. </blockquote></details> <details> <summary>src/components/SizePicker.tsx (1)</summary><blockquote> `10-14`: **i18n props integration — LGTM** Uses dictionary with sensible fallbacks; no behavioral regressions. Also applies to: 19-22 </blockquote></details> <details> <summary>src/app/(root)/products/page.tsx (1)</summary><blockquote> `4-6`: **Products root redirect — LGTM** Aligns with locale-prefixed products listing. </blockquote></details> <details> <summary>src/components/internationalization/middleware.ts (1)</summary><blockquote> `25-36`: **Confirmed — top-level matcher already excludes /api, /_next, and static/public assets.** src/middleware.ts exports config.matcher '/((?!api|_next|_static|favicon.ico|riyal.svg|shoes|static|.*\\.[a-zA-Z0-9]+$).*)' so localizationMiddleware will not run for API routes, Next internals, or files with extensions. </blockquote></details> <details> <summary>src/components/internationalization/use-locale.ts (1)</summary><blockquote> `27-36`: **LGTM! Clean implementation of locale detection** The `useLocale` hook correctly extracts the locale from params, provides appropriate fallback to the default locale, and efficiently derives RTL status and locale configuration. </blockquote></details> <details> <summary>src/components/Filters.tsx (2)</summary><blockquote> `20-42`: **LGTM! Well-structured i18n data** The filter options are cleanly refactored to use dictionary-driven data with proper id/label separation, making the component fully internationalized while maintaining the original filtering logic. --- `232-239`: **Mobile and desktop filter inputs: verify immediate sync** - Manually verify that toggling the mobile checkbox (src/components/Filters.tsx:232–239 — id={`m-gender-${g.id}`}) immediately updates the corresponding desktop checkbox (`gender-${g.id}`) without a page refresh. Both inputs may be present in the DOM (desktop hidden via CSS), so ensure checked state is derived from a single shared source (URL params or central state) or implement explicit sync; avoid duplicate HTML IDs. </blockquote></details> <details> <summary>src/components/Navbar.tsx (1)</summary><blockquote> `19-24`: **Good use of const assertion for type safety** The `as const` assertion on NAV_LINKS ensures type safety and immutability of the navigation structure. </blockquote></details> <details> <summary>src/app/[lang]/products/page.tsx (2)</summary><blockquote> `66-78`: **Price node: consistent currency formatting** Looks good. Using a single Riyal symbol for a min-max range is OK. No change needed. --- `11-17`: **Incorrect — params/searchParams should be Promises on Next.js 15+** Next.js 15 makes App Router params and searchParams asynchronous (Promise); typing them as Promise<T> and awaiting them in an async page is correct per the official upgrade docs/codemod. Do not remove Promise<> unless this project targets Next.js <15. ([nextjs.org](https://nextjs.org/docs/messages/next-prerender-sync-params?utm_source=openai)) > Likely an incorrect or invalid review comment. </blockquote></details> <details> <summary>src/app/[lang]/products/[id]/page.tsx (3)</summary><blockquote> `127-146`: **Gallery fallback logic is solid** Variant-specific images with sensible fallback and ordering. Nice. --- `210-217`: **Product details sections localized correctly** Good use of dictionary-driven content and defaults. --- `230-243`: **Suspense fallbacks are appropriate** Skeleton for recommendations and localized loading for reviews are well done. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="VcsDirectoryMappings"> | ||
| <mapping directory="" vcs="Git" /> | ||
| </component> | ||
| </project> No newline at end of file | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Remove IDE-specific metadata (.idea) from VCS; add to .gitignore.
This file is user/machine-specific and will create noisy diffs and merge conflicts. It’s unrelated to the i18n changes and should not be committed. Recommend deleting this file from the PR and ignoring the whole .idea directory (or, if you must share settings, commit only portable configs like .editorconfig).
Apply the ignore rule (at repo root):
--- a/.gitignore
+++ b/.gitignore
+/.idea/
+*.imlVerification script to confirm ignore rules and stray IDE files:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking for .idea in any .gitignore..."
fd -H '^\.gitignore$' -t f | xargs -I{} rg -n '^\s*\.idea/?\s*$' {} || echo "No .idea ignore found."
echo "Listing committed .idea artifacts..."
# Shows any .idea paths tracked by git in the current branch
git ls-files | rg -n '(^|/)\.idea(/|$)' || echo "No tracked .idea files."
echo "Listing untracked .idea artifacts (should be ignored after fix)..."
fd -H '.idea' -u -t d -E .git || trueLength of output: 294
Remove committed IDE metadata (.idea) and add ignore rule
Repo currently tracks .idea artifacts — remove them from git and add an ignore rule.
Tracked files (from verification): .idea/.gitignore, .idea/inspectionProfiles/Project_Default.xml, .idea/material_theme_project_new.xml, .idea/vcs.xml
Apply .gitignore change:
--- a/.gitignore
+++ b/.gitignore
+/.idea/
+*.imlFix locally:
printf ".idea/\n*.iml\n" >> .gitignore
git add .gitignore
git rm -r --cached .idea
git commit -m "Remove IDE metadata (.idea) and ignore .idea/ and *.iml"
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In .idea/vcs.xml lines 1-6 the repo is tracking IDE metadata; remove committed
.idea files and add ignore rules. Edit .gitignore to include a line for ".idea/"
and "*.iml", remove the tracked .idea files from the index with a recursive
cached remove (git rm -r --cached .idea) so they remain locally but are
untracked, then add and commit the updated .gitignore and the index change with
a message like "Remove IDE metadata (.idea) and ignore .idea/ and *.iml".
| const user = await getCurrentUser(); | ||
| const dictionary = await getDictionary(lang); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove PII logging and unused user fetch
Fetching the current user just to log it leaks PII to server logs and adds avoidable latency.
Apply:
-import { getCurrentUser } from "@/lib/auth/actions";
@@
-  const user = await getCurrentUser();
@@
-  console.log('USER:', user);Also applies to: 31-31
🤖 Prompt for AI Agents
In src/app/[lang]/page.tsx around lines 16-17 (and also line 31), remove the
call to getCurrentUser and any logging of the returned user to avoid PII
exposure and unnecessary latency; instead only await getDictionary(lang) (or
otherwise use the user data only where truly required), delete the
console.log/processLogger usage that prints user details, and adjust any
dependent code paths to not reference the removed user variable or to fetch user
information in a privacy-safe, on-demand location.
| export default async function ProductDetailPage({ | ||
| params | ||
| }: { | ||
| params: Promise<{ id: string; lang: Locale }> | ||
| }) { | ||
| const { id, lang } = await params; | ||
| const dictionary = await getDictionary(lang); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Next.js page props incorrectly typed as Promise
params should be a plain object. Remove Promise<> and the await.
-export default async function ProductDetailPage({
-  params
-}: {
-  params: Promise<{ id: string; lang: Locale }>
-}) {
-  const { id, lang } = await params;
+export default async function ProductDetailPage({
+  params
+}: {
+  params: { id: string; lang: Locale }
+}) {
+  const { id, lang } = params;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export default async function ProductDetailPage({ | |
| params | |
| }: { | |
| params: Promise<{ id: string; lang: Locale }> | |
| }) { | |
| const { id, lang } = await params; | |
| const dictionary = await getDictionary(lang); | |
| export default async function ProductDetailPage({ | |
| params | |
| }: { | |
| params: { id: string; lang: Locale } | |
| }) { | |
| const { id, lang } = params; | |
| const dictionary = await getDictionary(lang); | 
🤖 Prompt for AI Agents
In src/app/[lang]/products/[id]/page.tsx around lines 104 to 110, the page props
type incorrectly declares params as Promise<{ id: string; lang: Locale }> and
the code awaits params; change the props type to { params: { id: string; lang:
Locale } } (remove Promise<...>) and remove the await so you read params
directly (const { id, lang } = params), leaving the rest of the function
unchanged.
| {description && ( | ||
| <p className="text-sm text-dark-700"> | ||
| {dictionary && lang === 'ar' ? 'أحذية رياضية عالية الجودة' : description} | ||
| </p> | ||
| )} | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t override real product descriptions in Arabic.
The current logic replaces a provided description with a placeholder whenever lang === 'ar'. This discards real content.
-        {description && (
-          <p className="text-sm text-dark-700">
-            {dictionary && lang === 'ar' ? 'أحذية رياضية عالية الجودة' : description}
-          </p>
-        )}
+        {description && (
+          <p className="text-sm text-dark-700">
+            {description}
+          </p>
+        )}If you want a fallback message only when both description and subtitle are absent, add an extra branch for that case.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {description && ( | |
| <p className="text-sm text-dark-700"> | |
| {dictionary && lang === 'ar' ? 'أحذية رياضية عالية الجودة' : description} | |
| </p> | |
| )} | |
| {description && ( | |
| <p className="text-sm text-dark-700"> | |
| {description} | |
| </p> | |
| )} | 
🤖 Prompt for AI Agents
In src/components/Card.tsx around lines 69-73 the code unconditionally
substitutes any provided description with a hardcoded Arabic string when lang
=== 'ar', discarding real product descriptions; change the rendering logic so
that if description exists it is always displayed regardless of lang, and only
when description is missing (and subtitle is also missing) and lang === 'ar'
(and dictionary flag if needed) render the Arabic fallback; implement this by
branching: show description if truthy, else if !description && !subtitle && lang
=== 'ar' render the placeholder, otherwise render nothing.
| "sort": { | ||
| "title": "Sort By", | ||
| "featured": "Featured", | ||
| "newest": "Newest", | ||
| "priceHighToLow": "Price: High to Low", | ||
| "priceLowToHigh": "Price: Low to High" | ||
| }, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key mismatch with Sort component (sort.sortBy vs sort.title).
Sort.tsx reads dictionary.sort.sortBy, but this file only defines sort.title. This renders an undefined label.
Apply either fix; Option A is preferred to keep a single key:
Option A – use existing key in Sort.tsx:
-      <span className="text-body text-dark-900">{dictionary.sort.sortBy}</span>
+      <span className="text-body text-dark-900">{dictionary.sort.title}</span>Option B – add an alias key here (also add to ar.json to stay in sync):
   "sort": {
     "title": "Sort By",
+    "sortBy": "Sort By",
     "featured": "Featured",
     "newest": "Newest",
     "priceHighToLow": "Price: High to Low",
     "priceLowToHigh": "Price: Low to High"
   },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "sort": { | |
| "title": "Sort By", | |
| "featured": "Featured", | |
| "newest": "Newest", | |
| "priceHighToLow": "Price: High to Low", | |
| "priceLowToHigh": "Price: Low to High" | |
| }, | |
| "sort": { | |
| "title": "Sort By", | |
| "sortBy": "Sort By", | |
| "featured": "Featured", | |
| "newest": "Newest", | |
| "priceHighToLow": "Price: High to Low", | |
| "priceLowToHigh": "Price: Low to High" | |
| }, | 
🤖 Prompt for AI Agents
In src/components/internationalization/en.json around lines 100-106 the key used
by the Sort component is mismatched (file defines sort.title but Sort.tsx reads
dictionary.sort.sortBy); change Sort.tsx to reference dictionary.sort.title
(preferred Option A) so the label resolves correctly; after making this change,
run a repo-wide search for other uses of sortBy to update them if needed, or if
you choose Option B instead, add a sortBy alias in en.json (and mirror it in
ar.json) to keep locales in sync.
| const locale = (params?.lang as Locale) || 'en'; | ||
| const config = localeConfig[locale]; | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe locale cast can crash on unknown lang; add runtime guard
Casting route param to Locale trusts user input; config may be undefined and cause runtime errors.
Use a safe fallback:
-  const locale = (params?.lang as Locale) || 'en';
-  const config = localeConfig[locale];
+  const raw = params?.lang;
+  const candidate = typeof raw === 'string' ? raw : 'en';
+  const safeLocale = (candidate in localeConfig ? candidate : 'en') as Locale;
+  const config = localeConfig[safeLocale];
@@
-    html.lang = locale;
+    html.lang = safeLocale;
-    html.dir = config.dir;
+    html.dir = config.dir;
@@
-    const isRTL = config.dir === 'rtl';
+    const isRTL = config.dir === 'rtl';
@@
-  }, [locale, config.dir]);
+  }, [safeLocale, config.dir]);Also applies to: 12-31
🤖 Prompt for AI Agents
In src/components/LocaleProvider.tsx around lines 9 to 31, the code unsafely
casts params?.lang to Locale and then reads localeConfig[locale] which can be
undefined and crash; replace the direct cast with a runtime guard that checks if
params?.lang is a valid key in localeConfig (or use an isLocale type-guard), set
locale to params.lang when valid otherwise to the safe default 'en', and then
read config from localeConfig[locale]; ensure subsequent code uses the validated
config and does not assume non-null without a fallback.
| <span className="text-body text-dark-900">{dictionary.sort.sortBy}</span> | ||
| <select | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix i18n key and localize the aria‑label.
sort.sortBy doesn’t exist in en.json; use sort.title and make the aria label localized.
-      <span className="text-body text-dark-900">{dictionary.sort.sortBy}</span>
+      <span className="text-body text-dark-900">{dictionary.sort.title}</span>
...
-        aria-label="Sort products"
+        aria-label={dictionary.sort.title}Also applies to: 38-38
🤖 Prompt for AI Agents
In src/components/Sort.tsx around lines 33-34 and also line 38, the code uses
the non-existent i18n key dictionary.sort.sortBy and the select lacks a
localized aria-label; replace dictionary.sort.sortBy with dictionary.sort.title
and add an aria-label prop on the select that uses the appropriate localized
string (e.g., dictionary.sort.title or a dedicated dictionary.sort.ariaLabel
key), ensuring both visible text and the aria-label source the same i18n value;
update any other occurrence at line 38 similarly.
| export const config = { | ||
| matcher: ['/((?!api|_next|_static|favicon.ico|riyal.svg|shoes|static|.*\\.[a-zA-Z0-9]+$).*)'], | ||
| }; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand exclusions to avoid localizing Next metadata/image routes; remove over-broad custom skips.
Current matcher may intercept /opengraph-image, /icon, etc., and the explicit shoes exclusion can suppress real pages.
Apply this diff:
 export const config = {
-  matcher: ['/((?!api|_next|_static|favicon.ico|riyal.svg|shoes|static|.*\\.[a-zA-Z0-9]+$).*)'],
+  matcher: [
+    // Skip API, Next internals, and metadata/image/icon endpoints
+    '/((?!api|_next/static|_next/image|_vercel|favicon\\.ico|robots\\.txt|sitemap\\.xml|site\\.webmanifest|opengraph-image|icon|apple-icon|android-chrome|.*\\.[a-zA-Z0-9]+$).*)',
+  ],
 };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const config = { | |
| matcher: ['/((?!api|_next|_static|favicon.ico|riyal.svg|shoes|static|.*\\.[a-zA-Z0-9]+$).*)'], | |
| }; | |
| export const config = { | |
| matcher: [ | |
| // Skip API, Next internals, and metadata/image/icon endpoints | |
| '/((?!api|_next/static|_next/image|_vercel|favicon\\.ico|robots\\.txt|sitemap\\.xml|site\\.webmanifest|opengraph-image|icon|apple-icon|android-chrome|.*\\.[a-zA-Z0-9]+$).*)', | |
| ], | |
| }; | 
Summary by CodeRabbit
New Features
UI
Documentation
Chores