This file documents the codebase for AI agents (Claude, Codex, Cursor) and human contributors. Keep it in sync when files change in src/.
Torky Store Men's (متجر تركي للأحذية الرجالية) is an Arabic-first guest-checkout e-commerce storefront for selling men's shoes in Egypt. No auth this phase; orders are placed as guests and verified manually via Supabase Studio.
- Framework: Next.js 15 App Router + TypeScript + React 19
- Styling: Tailwind v4 (CSS variables, OKLCH), shadcn-style primitives in
src/ui - State: Zustand (
src/stores/cart-store.ts) withpersist→ localStorage - Server data: Supabase JS (
@supabase/ssr), TanStack Query for client cache - Forms: react-hook-form + Zod (
src/lib/validators) - Motion: framer-motion + tokens in
src/lib/motion.ts - Image: sharp (server invoice re-encode + favicon generator)
- PWA: Serwist (
src/app/sw.ts) - Lint/format: oxlint + oxfmt
- Hooks: husky pre-commit (lint+format+type-check), pre-push (build), commit-msg (commitlint)
src/
├── app/ # Next.js App Router
│ ├── layout.tsx # html lang=ar dir=rtl, Cairo font, providers
│ ├── page.tsx # → views/HomeView
│ ├── shoes/[customId]/page.tsx # → views/ShoeDetailView
│ ├── pay/page.tsx # → views/PayView
│ ├── success/[orderId]/page.tsx
│ ├── privacy/page.tsx
│ ├── terms/page.tsx
│ ├── offline/page.tsx
│ ├── not-found.tsx
│ ├── manifest.ts | robots.ts | sitemap.ts (dynamic Supabase) | sw.ts
│ ├── favicon.ico
│ └── api/
│ ├── orders/route.ts # POST guest order via place_order RPC
│ └── upload-invoice/route.ts # multipart, magic-bytes verify, sharp re-encode, Supabase storage
├── providers/ # ThemeProvider, PaletteProvider, OnlineProvider, QueryProvider
├── contexts/ # (reserved)
├── ui/ # button, input, label, textarea, badge, separator, sheet, radio-group
├── components/ # header, footer, skip-link, theme-switcher, color-palette-switcher,
│ # offline-indicator, cart-trigger, cart-sheet, qty-stepper,
│ # payment-receiver-hint, return-policy-notice, shoe-card
├── views/ # HomeView, PayView, ShoeDetailView (page-level compositions)
├── hooks/ # useCart, useShoesList, useShoeByCustomId, useColors, useSizes
├── services/ # shoes-service (queries, public image URL builder)
├── data/ # constants object — single source of all Arabic copy + tokens
├── types/ # IdString, IsoDateString, OptionalString
├── enums/ # PaymentMethodEnum, OrderStatusEnum, ColorPaletteEnum,
│ # ThemeModeEnum, PwaDisplayEnum, RobotsPolicyEnum
├── interfaces/ # ShoeInterface, OrderInterface, CartLineInterface, ...
├── lib/
│ ├── utils.ts # cn, formatEgpHandler, formatDateArHandler, isEgyptPhoneHandler, ...
│ ├── motion.ts # motionTokens (duration, ease curves)
│ ├── seo.ts # buildMetadataHandler, buildOrganizationLdHandler
│ ├── supabase/
│ │ ├── client.ts # createBrowserSupabaseClientHandler
│ │ ├── server.ts # createServerSupabaseClientHandler, createServiceRoleSupabaseClientHandler
│ │ └── types.ts # generated via Supabase MCP `generate_typescript_types`
│ └── validators/ # orderPayloadSchema (Zod), invoice MIME/size constants
└── stores/
└── cart-store.ts # Zustand + persist, addLineHandler, removeLineHandler, setQuantityHandler
Each folder has an index.ts barrel. Import from the barrel, not deep paths, except inside the same folder.
- Identifiers: camelCase for consts/vars/object keys.
- Components: PascalCase. Hooks:
use*prefix. - Event handlers: camelCase ending in
Handler(fileChangeHandler,clickHandler). - Pure lib functions: end in
Handler(formatEgpHandler,buildMetadataHandler). - No hardcoded strings: every user-visible string lives in
src/data/index.tsconstants object. - Enums:
*Enumsuffix; members SCREAMING_SNAKE_CASE; string values whatever they need to be. - Interfaces:
*Interfacesuffix. - Files: kebab-case (
shoe-card.tsx,use-cart.ts). - Object literal keys: alphabetically sorted (oxlint
sort-keys: error). - Functions: arrow expressions assigned to const, never
functiondeclarations. - Type imports:
import { type Foo }inline. - No deep cross-feature imports: go through barrels.
- Project:
xuobbsircuaomhsjlsyqinHawary's Organization, regioneu-central-1 - URL:
https://xuobbsircuaomhsjlsyq.supabase.co - Tables:
colors, sizes, categories, shoes, shoe_categories, shoe_variants, shoe_images, orders, order_items - RLS: catalog tables → public read; orders/order_items → no anon access (RPC-only path)
- RPC:
place_order(...)security definer; atomic stock validation + decrement + Arabic snapshots - Triggers:
trg_variant_stockkeepsshoes.in_stockin sync;trg_*_updated_atfor timestamps - Buckets:
shoes(public read),invoices(private, service-role only) - Type regeneration: rerun
mcp__claude_ai_Supabase__generate_typescript_typesafter schema changes; paste intosrc/lib/supabase/types.ts
- Conventional commits, types from
commitlint.config.js:feat, fix, docs, style, design, refactor, perf, test, build, ci, chore, revert - Header ≤ 100 chars
- No Claude/AI signature, no
Co-Authored-Byline - Body explains "why", not "what"
- Supabase MCP: list/create projects, apply migrations, generate types, advisors, storage
- Context7 MCP: fetch live framework/library docs
- Impeccable: design + brand context (PRODUCT.md, DESIGN.md)
NEXT_PUBLIC_SUPABASE_URL— public, in browser bundleNEXT_PUBLIC_SUPABASE_ANON_KEY— public, in browser bundleSUPABASE_SERVICE_ROLE_KEY— server only; used by/api/upload-invoiceand dynamic sitemap fetchNEXT_PUBLIC_SITE_URL— canonical origin for SEO + sitemap
| Route | Static / Dynamic | Notes |
|---|---|---|
/ |
static | shoe grid via TanStack Query → Supabase |
/shoes/[customId] |
dynamic | client-fetched detail + zoom + variant pickers |
/pay |
static (NOINDEX) | RHF + Zod, payment hint zone, invoice upload |
/success/[orderId] |
dynamic (NOINDEX) | post-order confirmation |
/privacy, /terms |
static | placeholder Arabic structure |
/offline |
static | SW fallback |
/manifest.webmanifest |
static | PWA manifest |
/robots.txt |
static | |
/sitemap.xml |
dynamic, revalidate=3600 |
queries Supabase shoes |
/api/orders |
route handler | POST → place_order RPC |
/api/upload-invoice |
route handler | POST multipart |
When files added/removed in src/, regenerate the Folder map section above. Until automated, do it by hand on PRs that touch folder structure.