Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
431 lines
23 KiB
Plaintext
431 lines
23 KiB
Plaintext
# 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
|
||
|