# Cursor Rules - Ghabilee Admin Panel ## Project Overview This is a Next.js 16 admin panel project using TypeScript, React 19, and Tailwind CSS. ## General Coding Standards ### TypeScript & React - Always use TypeScript with strict mode enabled - Use functional components with hooks (no class components) - Use `'use client'` directive for client components - Define proper TypeScript interfaces/types for all props, state, and data structures - Use type inference where appropriate, but be explicit for public APIs - Prefer `interface` over `type` for object shapes - Use React.FC or explicit return types for components when needed ### File Organization - Use path aliases (`@/`) for imports instead of relative paths - Follow the existing folder structure: - `app/` - Next.js app router pages and layouts - `components/` - Reusable React components (shared across multiple routes) - `context/` - React Context providers - `hooks/` - Custom React hooks - `services/` - API service functions - `validation/` - Zod validation schemas - `helpers/` - Utility functions - `types/` - TypeScript type definitions - `styles/` - Global styles and SCSS files ### Component Organization (Colocation Pattern) - **Page-specific components**: Create a `_components/` folder within each route directory - **Shared/reusable components**: Place in the global `components/` directory - The underscore prefix (`_`) makes the folder a private segment - it will never become part of the route structure - Example: ``` app/[locale]/(dashboard)/matches/ ├── _components/ # Private: Page-specific match components │ ├── MatchCard.tsx │ └── MatchFilters.tsx └── page.tsx ``` - Benefits: Easy to find related components, clear separation, easier refactoring ### Naming Conventions - Components: PascalCase (e.g., `AuthContext.tsx`, `Button.tsx`) - Files: Match component/export name - Functions: camelCase (e.g., `handleSubmit`, `fetchUser`) - Constants: UPPER_SNAKE_CASE (e.g., `API_ROUTES`) - Interfaces/Types: PascalCase (e.g., `User`, `AuthContextType`) ### Code Style - Use 2 spaces for indentation - Use single quotes for strings - No semicolons at the end of statements (enforced by Prettier with `"semi": false`) - Use trailing commas in multi-line objects/arrays - Keep lines under 140 characters (enforced by Prettier `printWidth: 140`) - Use destructuring for props and state - Prefer const over let, avoid var ### React Patterns - Use custom hooks for reusable logic (e.g., `useAuth`, `useLoading`) - Use Context API for global state management - Prefer composition over inheritance - Use React.memo() for performance optimization when needed - Extract complex logic into separate functions or hooks ### Form Handling - Always use `react-hook-form` with `@hookform/resolvers/zod` - Define validation schemas in `validation/` directory using Zod - Use `FormProvider` for form context - Handle form errors appropriately with user-friendly messages ### Zod Validation with next-intl - **CRITICAL: Zod schemas must NOT be static when using translations** - **Always define validation schemas as factory functions** that receive the translation function `t` - **Build the schema inside the component** (or server action) where `t` is available - This ensures: - ✅ Localized error messages - ✅ Compatibility with locale changes - ✅ Clean separation between validation logic and React **Pattern for Validation Files:** ```typescript import { z } from 'zod' type TFunc = (key: string) => string export const FormValidation = (t: TFunc) => z.object({ field: z.string().min(1, t('required')), email: z.string().email(t('invalidEmail')), }) ``` **Client Component Usage:** ```typescript 'use client' import { useTranslations } from 'next-intl' import { FormValidation } from '@/validation/form' const Component = () => { const tValidation = useTranslations('validation') const form = useForm({ resolver: zodResolver(FormValidation(tValidation)), }) // ... } ``` **Server Action Usage:** ```typescript import { getTranslations } from 'next-intl/server' import { FormValidation } from '@/validation/form' export async function serverAction() { const t = await getTranslations('validation') const schema = FormValidation(t) // schema.parse(...) } ``` **Key Rules:** - ❌ **DO NOT** define schemas statically: `export const Schema = z.object({...})` - ✅ **ALWAYS** use factory functions: `export const Schema = (t: TFunc) => z.object({...})` - ❌ **DO NOT** use `useTranslations` inside validation files (it's a React hook) - ✅ **ALWAYS** pass `t` as a parameter to the factory function - ✅ **CRITICAL: Only ONE validation object in fa.json** - **ONLY** use `validation` object in `messages/fa.json` for all validation messages - **NEVER** create nested validation objects like `project.validation`, `activity.validation`, etc. - **ALWAYS** add new validation keys to the main `validation` object - **ALWAYS** use `useTranslations('validation')` in context files, never use nested paths like `project.validation` ### API & Data Fetching - Keep API calls in `services/` directory - Use `axiosInstance` from `@/config/axios` for API requests - Always handle errors with try/catch blocks - Use async/await instead of promises chains - Show loading states using `useLoading` hook - Display user-friendly error messages using toast notifications - **CRITICAL: Do NOT use `/system` endpoints** - Endpoints that end with `/system` (such as `.../create/system`, `.../update/system`, `.../delete/system`) must not be used in this project. - Always use the non-system equivalent endpoint (for example, `.../create` instead of `.../create/system`). - If only a `/system` endpoint exists in docs, leave a `TODO` comment and ask for backend clarification before implementing. - **CRITICAL: Numeric IDs in backend payloads** - Every identifier sent to the backend in request bodies or query params that the API expects as a number (`project_id`, `user_id`, `city_id`, foreign keys, etc.) must be a **JavaScript `number`**, not a string. - URL/route params from Next.js (`useParams`, dynamic `[id]` segments) are **strings**; convert at the **service boundary** (or immediately before the axios call) with `Number(id)`, `parseInt(id, 10)`, or equivalent, and validate with `Number.isFinite` when the contract requires a valid id. - Prefer typing service payloads with `project_id: number` (and similar) so TypeScript enforces the contract; do not spread raw string params into POST bodies without conversion. ### Services & API Pattern - **Single source of data**: All API access goes through `services/`. Pages and components must **never** import mock JSON (e.g. `supplierMockData.json`) directly—always call service functions (e.g. `GET_PROJECTS`, `GET_SUPPLIERS`). - **CRITICAL: Paginated list meta — DRY** - Backend list endpoints follow `docs/list-endpoints.en.md`. - Query: `page`, `pageSize`, `sort`, `filters` (see `ListQueryDto` on backend). - Response inside `data`: `{ items, response: { page, pageSize, totalItemsCount, totalPages, sort, filters } }`. - **ALWAYS** reuse `ApiListMeta`, `PaginatedListMeta`, and `ApiPaginatedResult` from `@/types/apiListMeta`. - Parse API responses with `parseRemittanceList()` from `@/helpers` (unwraps `{ success, data }`). - For empty/fallback meta, use `createEmptyPaginatedListMeta(page, pageSize)`. - **CRITICAL: New services and backend-backed endpoints — NO mock** - When adding a **new** service file or wiring a feature to a **real backend endpoint**, implement **only the real API** via `axiosInstance` and `API_ROUTES` in `services/config.ts`. - **Do NOT** add mock branches (`isMockMode()`, `simulateApiDelay`, `maybeThrowMockError`), **do NOT** create `mocks//` folders, and **do NOT** import mock JSON/JS for that feature. - Follow the pattern in `services/activityOwner.ts`, `services/activityProvider.ts`, `services/projectMember.ts`, `services/projectProvider.ts`: POST/GET to backend, parse `{ success, body }`, `normalizeServiceError`, numeric IDs at the service boundary. - Legacy services may still contain mock code from earlier development; do not copy that pattern into new work—migrate to API-only when touching them. - **Error messages**: All service error texts live in **one** top-level `messages` object in `messages/fa.json` (e.g. `mockServerError`, `projectNotFound`, `mediaNotFound`, `ticketNotFound`, `supplierNotFound`, `contractNotFound`, `inquiryNotFound`, `inquiryLoadError`). Services reject with a **messageKey** (e.g. `messages.mockServerError`). In UI, use `getServiceErrorMessage(error, tMessages)` with `useTranslations('messages')`. - **Hooks**: A custom hook (e.g. `useProjects`) is **optional**. Create one only when multiple pages/components share the same data and loading/error state; otherwise call the service directly in the page or context (e.g. `useEffect` + state). ### Internationalization (i18n) - Always use `next-intl` for translations - Use `useTranslations` hook for accessing translations - Never hardcode text strings - always use translation keys - Translation keys should be in `messages/{locale}.json` files - Support locales: `en`, `fa`, `ar`, `ku` - **IMPORTANT: Development Phase i18n Rule** - The project is multilingual but currently in active development - **ONLY add new translation keys to `messages/fa.json`** - **ONLY read/use translations from `fa.json`** - Do NOT add translation keys to other locale files (`en.json`, `ar.json`, `ku.json`) during development - This rule applies until the project reaches a stable state - When adding new features, only update `fa.json` with new keys - **CRITICAL: Reuse Existing Translation Keys** - **ALWAYS check if a translation key already exists** in `messages/fa.json` before adding a new one - **NEVER duplicate translation keys** - if a key exists elsewhere, reuse it instead of creating a duplicate - **ONLY add new keys** for truly new content that doesn't exist anywhere else - When reusing existing keys, use multiple `useTranslations` hooks if needed: ```typescript // ✅ CORRECT - Reuse existing keys from different namespaces const tPersonnel = useTranslations('project.personnel.staff.projectUsers') const tPersonnelColumns = useTranslations('project.personnel.executive.humanResources.table.columns') // Use existing keys label: tPersonnelColumns('firstName') // Reused from existing namespace label: tPersonnel('table.columns.email') // New key, only in projectUsers ``` - **Example**: If `firstName`, `lastName`, `number`, `avatar`, `mobile`, `nationalCode` already exist in `executive.humanResources.table.columns`, do NOT add them again in `projectUsers.table.columns` - **Only add truly new keys** like `email` and `role` that don't exist elsewhere - **CRITICAL: Keep translation usage simple by default** - For service/UI errors, prefer direct usage: `getServiceErrorMessage(error, tMessages)`. - Do **NOT** introduce `useRef` wrappers like `tMessagesRef` by default. - Use a ref for translation functions only in rare, justified cases where you must avoid a specific effect/callback re-run and still need the latest translator. - If a ref is used, add a short comment in Persian explaining why direct `tMessages` is not sufficient in that spot. ### Error Handling - Always wrap async operations in try/catch blocks - Use `addToast` from `@heroui/toast` for user notifications - Show errors to users via `getServiceErrorMessage(error, tMessages)` (see **Services & API Pattern**). Do not commit `console.log`; avoid `console.error` in production code—prefer toast for user-facing errors - Provide meaningful error messages to users - Use finally blocks for cleanup (e.g., setLoading(false)) ### Delete actions (حذف) - هر جا دکمه یا عملیات **حذف** داده از سرور را انجام می‌دهد (endpointهای delete یا POST حذف)، **قبل از فراخوانی API** از کاربر تأیید بگیر. - الگوی ترجیحی: هوک **`useConfirmDelete`** از `@/hooks/useConfirmDelete` که روی `useAlertModal` سوار است و `requestDelete({ message, onConfirmed, confirmData? })` را با `{ dangerAccept: true }` صدا می‌زند؛ متن `message` را از `next-intl` بگیر (کلید موجود در `fa.json` در اولویت). - برای UI تکراری: **`DeleteConfirmIconButton`** (`@/components/delete/DeleteConfirmIconButton`) برای دکمهٔ آیکن حذف. - برای **`DropdownMenu` / منوی HeroUI**: فرزند باید مستقیماً **`DropdownItem`** از همان پکیج باشد (کامپوننت میانی دور `DropdownItem` با React Aria خطای `getCollectionNode` می‌دهد). از **`useConfirmDelete`** در همان کامپوننت والد و `onPress={() => requestDelete({ message, onConfirmed })}` روی `DropdownItem` استفاده کن؛ فراخوانی API فقط داخل `onConfirmed`. - اگر سناریو غیراستاندارد است، مستقیم `useAlertModal` + `showAlert` با همان قانون «تأیید قبل از API» مجاز است. ### Admin overlays (مودال، نه دراور) - در پنل ادمین (`app/(dashboard)/**`) از HeroUI `Drawer`، `AdminDetailDrawer` یا شیت کناری استفاده نکن. - بررسی مدرک، پیش‌نمایش، فرم و جزئیات رکورد روی همان صفحهٔ لیست باید با `import Modal from '@/components/modals/Modal'` وسط صفحه باز شود. - تأییدها همان `useAlertModal` بماند. ### Styling - Use Tailwind CSS utility classes for styling - Use SCSS files in `styles/` for complex styles - Follow mobile-first responsive design approach - Use HeroUI (NextUI) components when available - Maintain consistent spacing and design system - **Spacing utilities:** `px-2 py-2` → `p-2` (same for `m` and `gap-x`/`gap-y` when equal). Keep unequal axes (`px-4 py-3`). - **Breakpoint padding:** do not add `sm:`/`md:` padding or margin that is the same or one Tailwind step from the base (`p-4 md:p-5` → `p-4`). Keep real jumps (`px-4 sm:px-6`) and layout changes (`flex-col sm:flex-row`). - **Type size:** `text-xs sm:text-sm` (or `md:text-sm`) → keep `text-xs` only; do not bump caption/meta text at `sm`/`md`. - **Integer spacing:** do not use `.5` steps (`py-2.5`, `px-1.5`, `gap-0.5`). Round up to the next integer (`py-3`, `px-2`, `gap-1`). ### State Management - Use React Context for global state (AuthContext, GlobalContext, LoadingContext) - Use useState for local component state - Use useEffect for side effects with proper cleanup - Store user data in localStorage when needed - Use Cookies for authentication tokens ### Comments & Documentation - Write comments in Persian (Farsi) for complex logic - Add JSDoc comments for public functions and components - Keep comments concise and meaningful - Explain "why" not "what" in comments ### Performance - Use dynamic imports for code splitting when appropriate - Optimize images using Next.js Image component - Avoid unnecessary re-renders - Use useMemo and useCallback when needed for expensive operations ### Security - Never commit sensitive data (API keys, tokens) - Use environment variables for configuration - Validate all user inputs using Zod schemas - Sanitize data before displaying to prevent XSS ### Git & Version Control - Write clear, descriptive commit messages - Follow conventional commit format when possible - Use lint-staged and husky for pre-commit hooks - Keep commits focused and atomic ## Specific Patterns ### Chart.js Font Configuration - **CRITICAL: Always access font directly in Chart.js options, not at component level** - **DO NOT** store font in a variable at component level (e.g., `const persianFont = ...`) - **ALWAYS** use `window.getComputedStyle(document.body).fontFamily` directly in chart options - This ensures the font is loaded when Chart.js reads the options, not when the component first renders - Font may not be loaded when component initializes, causing incorrect font display **Correct Pattern:** ```typescript const chartOptions = { plugins: { tooltip: { bodyFont: { family: window.getComputedStyle(document.body).fontFamily, size: 12, }, titleFont: { family: window.getComputedStyle(document.body).fontFamily, size: 12, }, }, }, scales: { x: { ticks: { font: { family: window.getComputedStyle(document.body).fontFamily, size: 12, }, }, }, y: { ticks: { font: { family: window.getComputedStyle(document.body).fontFamily, size: 12, }, }, }, }, } ``` **Incorrect Pattern (DO NOT USE):** ```typescript // ❌ WRONG - Font may not be loaded when component renders const persianFont = typeof window !== 'undefined' ? window.getComputedStyle(document.body).fontFamily : 'sans-serif' const chartOptions = { plugins: { tooltip: { bodyFont: { family: persianFont, size: 12 }, }, }, } ``` **For Custom Plugins:** When using custom plugins (e.g., `afterDraw`), access font inside the plugin function: ```typescript const customPlugin = { id: 'customPlugin', afterDraw: (chart: ChartJS) => { const ctx = chart.ctx const loadedFont = window.getComputedStyle(document.body).fontFamily ctx.font = `bold 16px ${loadedFont}` // ... rest of plugin code }, } ``` ### Modal with Select Input - **When a Modal contains an Input with `generalType="select"`**: Use HeroUI Modal components from `@heroui/react`, NOT the custom Modal from `@/components/modals/Modal` - **ALWAYS** use: `import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@heroui/react'` - **NEVER** use: `import Modal from '@/components/modals/Modal'` when the modal has a select input - This ensures proper rendering and interaction of select dropdowns inside modals (the custom Modal wrapper can cause z-index/portal issues with select menus) ### Authentication - Use `AuthContext` for authentication state - Store tokens in Cookies and localStorage - Use `jwt-decode` for token decoding - Handle token expiration properly - **CRITICAL: Auth-required actions use the login modal, not `/auth` redirect** - Whenever a user action needs login (follow, bookmark, reserve, chat, profile-gated actions, etc.), call `requireAuth` from `useAuthGate()` (`@/context/AuthGateContext`). - Pattern: `requireAuth(() => { /* protected action */ })` — if the user is fully authenticated the action runs immediately; otherwise the AuthGate login modal opens on the current page and runs the pending action after success. - The route that renders the action **must** be under `SessionProviders` (which includes `AuthGateProvider`). If adding auth-gated UI to a public/SEO layout, wrap that layout with `SessionProviders`. - **NEVER** send the user to `/auth` or `/auth?redirect=...` for these in-page actions. - The dedicated `/auth` page is only for explicit auth entry (admin login flow, deep links that intentionally land on auth, etc.) — not for consumer CTA gates. ### Component Structure ```typescript 'use client' import { ... } from '...' interface ComponentProps { // props definition } const Component = ({ prop1, prop2 }: ComponentProps) => { // hooks // state // handlers // effects return ( // JSX ) } export default Component ``` ### Service Functions ```typescript import axiosInstance from '@/config/axios' export const SERVICE_FUNCTION = async (data: DataType) => { try { const res = await axiosInstance.post('/endpoint', data) return res } catch (error) { throw error } } ``` ## What NOT to Do - **Don't send string IDs to the backend** when the API expects numbers—normalize ids to `number` in `services/` before the request - Don't use `any` type - use proper types or `unknown` - Don't mix async/await with .then() chains - Don't create components without proper TypeScript types - Don't hardcode strings - use i18n - Don't ignore TypeScript errors - Don't commit console.log or console.error—use toast with getServiceErrorMessage for user-facing errors - Don't use inline styles when Tailwind classes are available - Don't create deeply nested components - extract into smaller components - **Don't define Zod validation schemas statically** - always use factory functions with `next-intl` - **Don't use `useTranslations` inside validation files** - it's a React hook and cannot be used in non-component files - **Don't put page-specific components in global `components/`** - use `_components/` folder within the route - **Don't create component folders without underscore prefix in routes** - use `_components/` not `components/` - **Don't duplicate translation keys** - always check if a key exists in `fa.json` before adding a new one, reuse existing keys instead - **Don't use `@/components/modals/Modal`** when the modal contains an Input with `generalType="select"` - use HeroUI Modal from `@heroui/react` instead - **Don't redirect to `/auth` for in-page auth gates** — use `requireAuth` from `useAuthGate()` so the login modal opens instead - **Don't import mock JSON** (e.g. `supplierMockData.json`) in pages or components—use service functions from `services/` - **Don't add mock layers to new services**—when a service is new or connected to the backend, use API-only (no `isMockMode`, no `mocks/`, no mock delay/error simulation) - **Don't call delete APIs** (or any destructive server delete) without prior user confirmation—use `useAlertModal` / `showAlert` first (see **Delete actions**) - **Don't use Drawer / `AdminDetailDrawer` in the admin panel** — use `Modal` from `@/components/modals/Modal` (see **Admin overlays**) - **Don't write `px-* py-*` when both values match** — use `p-*`; don't add breakpoint padding that only changes by one Tailwind step - **Don't pair `text-xs` with `sm:text-sm` / `md:text-sm`** — use `text-xs` at every breakpoint - **Don't use half-step spacing** (`p-2.5`, `px-1.5`, `mt-0.5`) — round up to an integer step - **Don't duplicate paginated list meta types** — use `ApiListMeta` / `ApiPaginatedResult` from `@/types/apiListMeta` and `createEmptyApiListMeta` instead of defining `{ total, page, limit, total_page }` per service ## When Adding New Features 1. Check existing patterns in the codebase first 2. Follow the established folder structure and colocation pattern: - Page-specific components → `_components/` within route directory - Shared components → global `components/` directory 3. Add proper TypeScript types 4. Add validation schemas if handling user input (use factory pattern with next-intl) 5. **Check for existing translation keys** in `messages/fa.json` before adding new ones - reuse existing keys when possible 6. Add translations for all user-facing text (only to `fa.json` during development) - **only add truly new keys that don't exist elsewhere** 7. Handle loading and error states; call services (API-only for new/backend features—see **Services & API Pattern**) and `getServiceErrorMessage` for errors 8. For new backend endpoints: add routes in `services/config.ts`, implement the service **without mock**, and ensure **ids in payloads are numbers** (convert route/query strings in the service layer) 9. Test the feature thoroughly