# {{ProjectName}} Web - React Frontend Memory

## Purpose

React SPA frontend. Communicates with {{ProjectName}}.Api via REST + SignalR.

## Tech Stack

| Technology | Version | Purpose |
|------------|---------|---------|
| React | ^18 / ^19 | UI library |
| TypeScript | ~5.9.3 | Type safety (strict mode) |
| Vite | ^7.2 | Build tool + dev server |
| React Router | ^6 / ^7 | Routing (lazy-loaded) |
| Context API | - | Client + server state |
| Axios | ^1.13 | HTTP client |
| Tailwind CSS | ^4.1 | Styling |
| i18next | ^25.7 | Internationalization |
| SignalR | ^10.0 | Real-time updates |
| Lucide React | ^0.562 | Icons |

---

## CRITICAL: Internationalization (i18n) - 4 Languages Required

### Rules

- **CRITICAL**: Every page MUST use translations via `useTranslation()` hook
- **CRITICAL**: All user-visible text MUST be in translation files (fr, en, it, de)
- **NEVER** hardcode text strings in components
- **ALWAYS** add translations to ALL 4 locales: `fr/`, `en/`, `it/`, `de/`

### Supported Languages

| Language | Code | Folder |
|----------|------|--------|
| French | `fr` | `locales/fr/` |
| English | `en` | `locales/en/` |
| Italian | `it` | `locales/it/` |
| German | `de` | `locales/de/` |

### Structure

```
src/i18n/
├── config.ts                    → i18next configuration
└── locales/
    ├── en/
    │   ├── common.json          → Common UI (buttons, labels, errors)
    │   ├── navigation.json      → Menu and navigation
    │   └── {feature}.json       → Feature-specific translations
    ├── fr/
    │   └── ...                  → Same structure as en/
    ├── it/
    │   └── ...                  → Same structure as en/
    └── de/
        └── ...                  → Same structure as en/
```

### Usage in Pages

```tsx
import { useTranslation } from 'react-i18next';

export function ExamplePage() {
  const { t } = useTranslation('feature');  // Specify namespace

  return (
    <div>
      <h1>{t('pageTitle')}</h1>
      <p>{t('description')}</p>
      <button>{t('common:save')}</button>  {/* Cross-namespace */}
    </div>
  );
}
```

### Adding New Translations

**ALWAYS add to ALL 4 languages** (en, fr, it, de) with the same structure.

### Translation Keys Convention

- Use **dot notation** for nested keys: `orders.pageTitle`
- Use **camelCase** for key names
- Group by feature/section: `orders.table.headerName`
- Common actions in `common` namespace: `common:save`, `common:cancel`

---

## Structure

```
{{ProjectNameLower}}-web/
├── src/
│   ├── main.tsx                 → Entry point
│   ├── App.tsx                  → Root component + providers + DynamicRouter
│   ├── i18n/                    → Internationalization
│   │   ├── config.ts            → i18next setup
│   │   └── locales/             → Translation files (fr/en/it/de)
│   ├── services/                → API client layer
│   │   └── api/
│   │       ├── apiClient.ts     → Axios instance + interceptors
│   │       └── {feature}Api.ts  → Feature-specific API modules
│   ├── components/              → Shared & domain components
│   │   ├── ui/                  → Base UI components (DataTable, Modal, etc.)
│   │   ├── layout/              → Layout components
│   │   ├── routing/             → Route guards + dynamic routing
│   │   └── {feature}/           → Feature-specific components
│   ├── pages/                   → Page components
│   │   └── {application}/{module}/ → Organized by navigation hierarchy
│   ├── contexts/                → React Context providers
│   ├── hooks/                   → Custom hooks
│   ├── layouts/                 → Layout wrappers
│   ├── utils/                   → Utility functions
│   ├── types/                   → Global types
│   ├── routes/                  → Route definitions
│   └── extensions/              → Extension system (PageRegistry)
├── tests/                       → Vitest tests (NOT in src/)
│   ├── utils/                   → Pure utility tests
│   ├── components/              → Component tests
│   ├── hooks/                   → Hook tests
│   └── services/                → Service tests
├── vite.config.ts
├── vitest.config.ts
├── tsconfig.json
└── eslint.config.js
```

## Patterns

### Page Template with i18n

```tsx
// src/pages/{module}/{PageName}Page.tsx
import { useTranslation } from 'react-i18next';

export function ExamplePage() {
  const { t } = useTranslation('feature');

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold">{t('example.pageTitle')}</h1>
      <p className="text-muted-foreground">{t('example.description')}</p>

      <div className="mt-4 flex gap-2">
        <button className="btn-primary">{t('common:save')}</button>
        <button className="btn-secondary">{t('common:cancel')}</button>
      </div>
    </div>
  );
}
```

### API Client

```typescript
// src/services/api/apiClient.ts — uses Axios instance
import { api } from '@/services/api/apiClient';

// Request interceptors automatically add:
// - Authorization: Bearer {token}
// - X-Tenant-Slug: {currentTenantSlug}
// - Accept-Language: {i18nextLng}
// - X-Correlation-ID: {UUID}

// Usage in service files:
const orders = await api.get<Order[]>('/orders');
const order = await api.post<Order>('/orders', { name, amount });
```

### API Service File Pattern

```typescript
// src/services/api/{feature}Api.ts
import { api } from './apiClient';

export interface OrderDto {
  id: string;
  name: string;
  amount: number;
  isActive: boolean;
}

export const orderApi = {
  getAll: (search?: string) =>
    api.get<OrderDto[]>('/orders', { params: { search } }),

  getById: (id: string) =>
    api.get<OrderDto>(`/orders/${id}`),

  create: (data: Partial<OrderDto>) =>
    api.post<OrderDto>('/orders', data),

  update: (id: string, data: Partial<OrderDto>) =>
    api.put<OrderDto>(`/orders/${id}`, data),

  delete: (id: string) =>
    api.delete(`/orders/${id}`),
};
```

### Custom Hook Pattern

```typescript
// src/hooks/useOrderDetail.ts
import { useState, useCallback } from 'react';
import { orderApi, OrderDto } from '@/services/api/orderApi';

export function useOrderDetail(id: string) {
  const [data, setData] = useState<OrderDto | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const load = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const result = await orderApi.getById(id);
      setData(result);
    } catch (err: unknown) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setLoading(false);
    }
  }, [id]);

  return { data, loading, error, load };
}
```

### Context Provider Pattern

```typescript
// src/contexts/ExampleContext.tsx
import { createContext, useContext, useState, type ReactNode } from 'react';

interface ExampleContextType {
  value: string;
  setValue: (v: string) => void;
}

const ExampleContext = createContext<ExampleContextType | null>(null);

export function ExampleProvider({ children }: { children: ReactNode }) {
  const [value, setValue] = useState('');
  return (
    <ExampleContext.Provider value={{ value, setValue }}>
      {children}
    </ExampleContext.Provider>
  );
}

export function useExample() {
  const ctx = useContext(ExampleContext);
  if (!ctx) throw new Error('useExample must be used within ExampleProvider');
  return ctx;
}
```

## UI Styling Patterns

### Semantic Color Usage

| Color | CSS Variables | Usage |
|-------|---------------|-------|
| `blue` | `--info-*` | Totals, informational, in progress |
| `green` | `--success-*` | Completed, resolved, active |
| `yellow` | `--warning-*` | Pending, on hold, warnings |
| `red` | `--error-*` | Errors, critical, failed |
| `neutral` | `--bg-secondary`, `--text-primary` | Inactive, closed, disabled |

---

## Commands

```bash
# Development
npm run dev              # Start dev server (port 6173)

# Build
npm run build            # Production build

# Tests (MANDATORY before commit)
npm test                 # Run all tests
npm run test:watch       # Watch mode
npm run test:coverage    # Coverage report

# Lint
npm run lint             # ESLint check
npm run typecheck        # TypeScript type check
```

## Routing

### Lazy Loading

All non-critical pages use `React.lazy()` for code splitting:

```tsx
const OrdersPage = lazy(() =>
  import('@/pages/{module}/OrdersPage').then(m => ({ default: m.OrdersPage }))
);
```

### Route Guards (Layered)

```
Auth check → Tenant transition → Permission check → Render
```

---

## App chrome comes from the package — including the mobile shell

`@atlashub/smartstack` renders the application chrome around `<DynamicRouter />`.
**Never build a local `AppShell` / `Sidebar`**: a second chrome inside the
package's is a permanent drift source, and it will not carry any of the mobile
behaviour below.

| Viewport | Rendered by the package |
|---|---|
| Desktop | App header, hierarchical sidebar (nav read from `/api/navigation/menu`), tenant switcher, user menu, content outlet. |
| Mobile | The "descente par paliers" shell: Applications → Modules → Sections → the resource page, a header with hierarchical back + breadcrumbs, the tenant chip + sheet, transverse search, offline banner + outbox sheet, and "Reprendre" cards. |

The mobile shell is driven by the **transverse bottom bar** — four fixed
entries, always present whatever the palier:

| Tab | What it shows |
|---|---|
| **Applications** | The palier navigation root (this app's DB menu). |
| **Tâches** | `GET /api/me/tasks` — every queue the caller owns, across modules. |
| **Activité** | `GET /api/me/activity` — the caller's recent events. |
| **Compte** | Profile, tenant, language, theme, sign-out. |

Both aggregates already carry the platform queues. To merge this app's own
entities into them, register from the Infrastructure DI (backend):
`AddExtensionTasks<ExtensionsDbContext>(tasks => tasks.Entity<Order>(…)…)` —
declarative, the mirror of `AddExtensionSearch` — or a hand-written
`ITaskProvider` / `IActivityProvider` via `AddSmartStackTaskProvider<T>()` /
`AddSmartStackActivityProvider<T>()`. **Nothing to write on the frontend.**

**The shell is ON by default** — nothing to turn on. The optional
`<SmartStackProvider config={{ …, mobile: { … } }}>` block (written by `/pwa`)
makes the choice explicit and is the only way to set `breakpoint`,
`shellComponent` or `bottomNav`; `enabled: false` opts out entirely.

What decides whether one of YOUR pages shows up inside the shell is its
`PageMobileMeta` — the third argument of `PageRegistry.register`, emitted from
the page's `pwa.support`. A page with no mobile metadata is desktop-only and is
hidden from the mobile menu. The palier navigation itself is the app's own DB
menu; there is no mobile-specific navigation model to author.

---

## Rules

1. **CRITICAL: i18n Required** - ALL pages must use `useTranslation()` with all 4 language files (fr/en/it/de)
2. **Pages in `pages/`**, components in `components/`** - organized by application/module
3. **Custom hooks for logic** - extract API calls and state management from components into `hooks/`
4. **Context API for global state** - use `contexts/` for auth, tenant, navigation, theme, etc.
5. **Axios API client** - all HTTP calls via `services/api/apiClient.ts`, never raw `fetch()`
6. **TypeScript strict mode** - no `any` types
7. **Tailwind for styling** - no CSS files, no inline styles (`style={{}}`)
8. **Functional components only** - no class components
9. **Composition over inheritance** - use props and children
10. **Tests in `tests/`** - NOT in `src/`, use Vitest + Testing Library

## CRITICAL: Tabs Must Persist in URL

When creating a page with tabs, **ALWAYS** persist the active tab in the URL query params (`?tab=xxx`).
Use the `useTabNavigation` hook.

## When Adding New Feature

1. Create page in `pages/{application}/{module}/`
2. Create components in `components/{feature}/`
3. Create API service in `services/api/{feature}Api.ts`
4. Create custom hooks in `hooks/use{Feature}.ts`
5. Register page via `PageRegistry.register()`
6. **CRITICAL**: Add translations to ALL 4 locales: `i18n/locales/{fr,en,it,de}/`
