admin/.cursorrules
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

431 lines
23 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<T>` 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/<feature>/` 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<T>` 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