Skip to content

Conversation

@abdout
Copy link

@abdout abdout commented Sep 15, 2025

Summary by CodeRabbit

  • New Features

    • Introduced full localization (English and Arabic) with RTL support and a language switcher.
    • Locale-aware routing and metadata across Home, Products, Product Detail, and Auth pages.
    • Prices now display in SAR with a Riyal symbol.
  • UI

    • Refreshed Navbar and Footer with localized links.
    • Updated Cards, Filters, and Sort for i18n.
    • New homepage sections: Hero, Trending, and Bottom Hero.
    • Switched to Inter and Rubik fonts for better LTR/RTL readability.
  • Documentation

    • Added i18n implementation guide and Claude usage notes.
  • Chores

    • Middleware-based locale detection and supporting dependencies.

abdout and others added 3 commits September 14, 2025 22:59
- 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>
@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

Walkthrough

This 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

Cohort / File(s) Summary
IDE and Tooling Config
/.claude/settings.local.json, /.idea/inspectionProfiles/Project_Default.xml, /.idea/vcs.xml, /CLAUDE.md, /package.json
Adds Claude local permissions; introduces IDE inspection and VCS mappings; adds i18n documentation; updates dependencies for i18n and UI (negotiator, intl-localematcher, radix slot, class utilities, lucide-react, query-string, tailwind-merge, zod; @types/negotiator).
Root and Auth Redirect Entrypoints
/src/app/(auth)/sign-in/page.tsx, /src/app/(auth)/sign-up/page.tsx, /src/app/(root)/layout.tsx, /src/app/(root)/page.tsx, /src/app/(root)/products/page.tsx, /src/app/(root)/products/[id]/page.tsx
Replaces UI with server redirects to locale-prefixed routes; removes Navbar/Footer from non-localized root layout.
Locale-Aware App Structure
/src/app/[lang]/layout.tsx, /src/app/[lang]/page.tsx, /src/app/[lang]/products/page.tsx, /src/app/[lang]/products/[id]/page.tsx, /src/app/[lang]/sign-in/page.tsx, /src/app/[lang]/sign-up/page.tsx
Adds i18n layout with metadata and static params; implements localized home, products list/detail, and auth pages using dictionaries and lang-aware links.
i18n Infrastructure
/src/components/internationalization/* (README.md, en.json, ar.json, config.ts, dictionaries.ts, middleware.ts, use-locale.ts), /src/middleware.ts
Introduces i18n config/types, dictionaries (EN/AR), server loader, client hooks, and middleware for locale detection/redirects; project-level middleware delegates to localization middleware with matcher.
Components: i18n and New UI
/src/components/AuthForm.tsx, /src/components/Card.tsx, /src/components/Filters.tsx, /src/components/Footer.tsx, /src/components/LanguageSwitcher.tsx, /src/components/LocaleProvider.tsx, /src/components/Navbar.tsx, /src/components/RiyalSymbol.tsx, /src/components/SizePicker.tsx, /src/components/SocialProviders.tsx, /src/components/Sort.tsx, /src/components/site/* (hero.tsx, trend.tsx, bottom-hero.tsx), /src/components/index.ts
Refactors major components to accept dictionary/lang; adds language switcher and locale provider; introduces Riyal currency symbol; updates labels/links; adds site sections (Hero, Trend, BottomHero); re-exports new components.
Global Fonts and Styles
/src/app/layout.tsx, /src/app/globals.css
Switches to Inter/Rubik with CSS variables; adjusts html/body attrs; adds utility classes for fonts and RTL/LTR handling.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

A hop to “/en”, a skip to “/ar” we go,
Middleware moonlights, setting locale’s glow.
Navbar learns new words, Filters sing along,
Riyals sparkle softly in the price-song.
Hero waves hello, Trend struts the floor—
This bunny speaks two tongues, and asks for more! 🥕🌍

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "Local" is too short and generic to describe the substantial changes in this PR; the diff adds internationalization (i18n), locale-aware routing and middleware, new localized pages/layouts, and many component and resource updates, so a reviewer cannot determine the main change from the title alone. Rename the PR to a concise, descriptive title that reflects the primary changes, for example: "Add i18n and locale-aware routing (en/ar) with localized pages and components" or "Introduce internationalization, locale middleware, and localized layouts".
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

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.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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” prompts

Allowing 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-only

If 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 drift

Declare 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 props

Hard-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 sizing

Switching 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 semantics

Use 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 tokens

Prefer 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 ergonomics

Make 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 variant

Keep 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 tree

Specify 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 consistency

Docs 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 block

Label 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 consistency

This guide uses pnpm while CLAUDE.md uses npm. Add a note or dual commands to avoid confusion.


388-456: Fix import path in layout snippet

Matches 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 alignment

Same 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 path

Standardize 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 fence

Add typescript language to satisfy markdownlint MD040.

-```
+```typescript
 // ✅ CORRECT: Server components use getDictionary

Apply 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 import

Next.js App Router doesn’t require React import in TSX files.

-import React from "react";

41-53: Optional: handle empty product list gracefully

If 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 intent

Consider 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 safety

Dictionary 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 Dictionary

Also 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 in useSwitchLocaleHref

The 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 to ar would work correctly. However, if you have a path like /en/arena/products and try to switch from en to ar, it might incorrectly produce /arena/products instead of /ar/arena/products because the en in "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 standard Content-Language HTTP header

The metadata uses a non-standard accept-language in the other field. 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 standard locale property:

     other: {
-      'accept-language': lang,
     },
+    // Consider setting this in middleware.ts instead for proper HTTP headers

52-54: Type assertion concern with locale keys

Using Object.keys(localeConfig) returns string[], not Locale[]. 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 after if statement

Minor formatting: missing space after if keyword.

-      if(result?.ok) router.push(`/${lang}`);
+      if (result?.ok) router.push(`/${lang}`);

135-141: Internal links should use Next.js Link component

The Terms of Service and Privacy Policy links use anchor tags (<a>) instead of Next.js Link component, 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 functionality

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

  1. Search functionality with a search overlay/modal
  2. Shopping cart state management and UI

104-114: Mobile view action buttons need onClick handlers

The 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 brand

Logo 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 root

Every 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 invalid

wa.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 keys

Using 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/priceMax

Current logic can throw on empty query items (e.g., gender[]=) and ignores priceMin/priceMax. Use normalized parsed filters 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 generation

If Filters builds URLs, pass lang to 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: avoid any for dictionary and add lang to reviews for proper date locale

Use the Dictionary type and pass lang into ReviewsSection so 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 be undefined, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce813e and 41a0cc1.

⛔ Files ignored due to path filters (10)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/riyal.svg is excluded by !**/*.svg
  • public/site/bottom-hero.png is excluded by !**/*.png
  • public/site/hero.png is excluded by !**/*.png
  • public/site/trend-three.png is excluded by !**/*.png
  • public/site/trend-two.png is excluded by !**/*.png
  • public/site/trend.png is excluded by !**/*.png
  • static/uploads/shoes/33e0cc6f-555e-465f-b3f3-441b7b7864b3-shoe-1.jpg is excluded by !**/*.jpg
  • static/uploads/shoes/bb937b63-189e-4acd-b3c0-ce5469fc03a1-shoe-1.jpg is 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 setup

Classes map cleanly to --font-inter and --font-rubik.

src/components/SocialProviders.tsx (1)

1-27: Verified — translation keys exist

auth.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 pipefail

echo "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"
fi

echo
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]+$" || true

if [ -f src/components/internationalization/README.md ]; then
echo
echo "=== README snippet (lines 270-284) ==="
sed -n '270,284p' src/components/internationalization/README.md || true
fi

echo
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 -->

Comment on lines +1 to +6
<?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
Copy link

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/
+*.iml

Verification 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 || true

Length 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/
+*.iml

Fix 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".

Comment on lines +16 to +17
const user = await getCurrentUser();
const dictionary = await getDictionary(lang);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Comment on lines +104 to +110
export default async function ProductDetailPage({
params
}: {
params: Promise<{ id: string; lang: Locale }>
}) {
const { id, lang } = await params;
const dictionary = await getDictionary(lang);
Copy link

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.

Suggested change
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.

Comment on lines +69 to +73
{description && (
<p className="text-sm text-dark-700">
{dictionary && lang === 'ar' ? 'أحذية رياضية عالية الجودة' : description}
</p>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
{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.

Comment on lines +100 to +106
"sort": {
"title": "Sort By",
"featured": "Featured",
"newest": "Newest",
"priceHighToLow": "Price: High to Low",
"priceLowToHigh": "Price: Low to High"
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
"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.

Comment on lines +9 to +11
const locale = (params?.lang as Locale) || 'en';
const config = localeConfig[locale];

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Comment on lines +33 to 34
<span className="text-body text-dark-900">{dictionary.sort.sortBy}</span>
<select
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Comment on lines +5 to +7
export const config = {
matcher: ['/((?!api|_next|_static|favicon.ico|riyal.svg|shoes|static|.*\\.[a-zA-Z0-9]+$).*)'],
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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]+$).*)',
],
};

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.

1 participant