# Страница профиля (`/profile`)

Личный кабинет пользователя: показывает данные аккаунта, баланс, адреса кошельков
и даёт доступ к мнемонике. Маршрут `ROUTES.PROFILE` (`/profile`), защищён
`ProtectedRoute` (только для авторизованных).

## Главная идея

Страница — **тонкая композиция виджетов**. В `ProfilePage.tsx` нет ни хуков данных,
ни стейта, ни дериваций — только разметка. Вся логика и презентация живут в слайсе
`widgets/profile` и в нижних слоях (`features/auth`, `features/wallet`, `shared`).
Это сознательный результат рефакторинга под FSD (`app → pages → widgets → features →
entities → shared`; нижние слои не импортируют верхние).

Ключевой приём: **каждый под-виджет сам тянет свои данные** через хуки. Прокидывать
`data` пропсами со страницы не нужно — `useMe` использует `queryKey: ['me']` со
`staleTime/gcTime: Infinity`, поэтому вызов `useMe()` в нескольких компонентах
дедуплицируется в один сетевой запрос. Та же логика, что в `WalletPage`.

## Карта файлов

```
pages/profile/
  ui/ProfilePage.tsx         ← композиция (без логики)
  ui/ProfilePage.module.css  ← только внешний каркас: .page .main .sections
  index.ts                   ← export { ProfilePage }
  README.md                  ← этот файл

widgets/profile/             ← ВСЁ содержимое страницы живёт здесь
  model/usePhoneField.ts     ← логика редактирования телефона + стейт нотификации
  ui/
    ProfileSummary.tsx       ← аватар + имя + баланс (self-fetch: useMe, usePortfolio)
    ProfileDetails.tsx       ← выбирает ветку individual/legal (self-fetch: useMe)
    IndividualFields.tsx     ← поля физлица; зовёт usePhoneField + рендерит Notification
    LegalEntityFields.tsx    ← поля юрлица (read-only, из data.legal_entity)
    SecuritySection.tsx      ← адреса кошельков (self-fetch: useWalletAddresses)
    MnemonicSection.tsx      ← кнопка → ROUTES.SEED_PHRASE
    KycBanner.tsx            ← баннер, если !kyc_verified (self-fetch: useMe)
    ProfileAvatar.tsx        ← загрузка/кроп аватара (был раньше)
    AvatarCropModal.tsx      ← модалка кропа (был раньше)
    ProfileSection.tsx       ← обёртка-карточка: title + children + actions (был раньше)
    fieldsGrid.module.css    ← общие .grid2/.grid1 (для Individual/Legal/Security)
    *.module.css             ← по одному на компонент с собственными стилями
  index.ts                   ← барель: экспортит верхнеуровневые компоненты

features/auth/lib/profile.ts ← getFullName / getDisplayName / isLegalEntity (над MeResponse)
shared/lib/utils/formatUsd.ts← formatUsd(value) → "$1,234.56" | "$—"
```

`IndividualFields` и `LegalEntityFields` — **внутренние** для виджета: в барель
(`widgets/profile/index.ts`) не выносятся, их использует только `ProfileDetails`
относительным импортом.

## Поток данных

| Источник | Откуда | Что даёт |
|---|---|---|
| `useMe()` | `@features/auth` | `MeResponse`: ФИО, email, паспорт, ИНН, `account_type`, `kyc_verified`, `legal_entity` |
| `usePortfolio()` | `@features/wallet` | `portfolio.totalUsd` для баланса |
| `useWalletAddresses()` | `@features/wallet` | массив `{ chain, address }` |
| `useUpdatePhone()` | `@features/auth` | мутация сохранения телефона (внутри `usePhoneField`) |

`MeResponse` определён в `features/auth/api/profileApi.ts`. Намеренно **не** перенесён
в пустой `entities/user` — это была бы отдельная миграция (затрагивает все импорты
типа). Если когда-нибудь будете наполнять `entities/user`, тип и хелперы из
`features/auth/lib/profile.ts` — первые кандидаты на переезд вниз.

## Развилка individual / legal

`ProfileDetails` решает по `isLegalEntity(data)` (`account_type !== 'individual'`):
- **individual** → `IndividualFields`: ФИО/email/паспорт/телефон + ИНН/ID. Телефон
  **редактируемый**.
- **legal** → `LegalEntityFields`: данные организации/адреса/контакты из
  `data.legal_entity` (если `legal_entity` пуст → рендерит `null`). Всё **read-only** —
  данные юрлица управляются на стороне админа.

## Логика телефона (`usePhoneField`)

Единственная нетривиальная логика на странице, живёт в `widgets/profile/model/usePhoneField.ts`
(по образцу `widgets/login-form/model/useLoginForm.ts`). Хук владеет:
- локальным `phone`, синхронизируемым с серверным значением через `useEffect`;
- санитизацией ввода (`onPhoneChange` — regex оставляет только `\d + пробел () -`);
- сохранением **по blur** (`onPhoneBlur`) с guard'ами: не сохраняет, если значение не
  менялось или мутация уже в полёте;
- стейтом нотификации (успех/ошибка) — она побочный эффект мутации, вне хука смысла нет.

Рендер `<Notification>` — в `IndividualFields` (хук владеет стейтом, компонент —
рендером). Это тост из `@shared/ui`, позиционируется фиксированно.

## CSS

- `ProfilePage.module.css` — только внешний каркас страницы (flex-колонка, центрирование
  `.main`, отступы, брейкпоинты 1023/639px для паддингов).
- Стили секций — рядом со своими компонентами в `widgets/profile/ui/*.module.css`.
- `fieldsGrid.module.css` — единственный осознанно общий стиль (`.grid2` 2 колонки →
  1 колонка на <640px; `.grid1`), импортится из Individual/Legal/Security. Лежит
  **внутри виджета**, поэтому ни один компонент не лезет в стиль страницы.
- Брейкпоинты по проекту: 1023 (десктоп→планшет), 649/639 (телефон), 549 (узкий телефон).

## Правила при доработке

1. **Страница остаётся тонкой.** Новый блок профиля = новый компонент в
   `widgets/profile/ui/` + экспорт в барель + вставка в `ProfilePage.tsx`. Не тащите
   хуки/стейт обратно в страницу.
2. **Данные — через self-fetch хуки**, не через пропсы со страницы (дедуп по queryKey).
3. **Деривации над `MeResponse`** → `features/auth/lib/profile.ts` (там же тип). Не
   дублируйте форматирование имени/`isLegal` в компонентах.
4. **Форматирование `$`-сумм** → `formatUsd` из `@shared/lib/utils/formatUsd`.
5. Соблюдайте порядок слоёв: виджеты импортируют из features/shared, не наоборот.

## Известные точки расширения / TODO

- `ProfileSummary`: закомментирован рублёвый баланс (`.userBalanceRub`, «≈ … ₽») —
  ждёт реального источника курса.
- `SecuritySection`: кнопки «⚠️ Посмотреть приватный ключ» и «СОХРАНИТЬ» сейчас
  **без обработчиков** (заглушки из исходной вёрстки). При реализации навесить логику.
- `formatUsd` дублируется инлайном ещё в 5 местах (BalanceCard, WalletHeader,
  TokenTable, useChainTokenRows, PoolsTable) — отдельная задача на дедупликацию.
  Внимание: часть из них использует вариант с 4–6 знаками, а не фиксированные 2 —
  при миграции, возможно, понадобится расширить `formatUsd` опциями точности.

## Проверка

```bash
npm run build   # tsc -b + vite build (тестов в проекте нет)
npm run lint
npm run dev      # ручная проверка /profile
```

Ручной чек-лист `/profile`:
- individual: поля видны; ввод телефона санитизируется; по blur сохраняется и
  появляется тост «Номер телефона обновлён» (и тост ошибки при сбое);
- legal: секции организации/адресов/контактов; телефон read-only;
- баланс через `formatUsd` (и `$—` при загрузке);
- KYC-баннер виден только при `!kyc_verified`;
- «Мнемоника» ведёт на `ROUTES.SEED_PHRASE`;
- адаптив на брейкпоинтах 1023/649/639/549 не сломан.
