# Модуль: Виджет для бизнеса

[← К оглавлению](../README.md)

Встраиваемая лента объявлений Placeo на чужой сайт **одним тегом `<script>`** — без iframe, с изоляцией стилей и подачей объявлений прямо с чужого сайта.

## Сниппет

```html
<script src="http://placeo.ru/widget/placeo-widget.js" data-key="pw_..."></script>
```

## Загрузчик

`public/widget/placeo-widget.js` (vanilla) + `public/widget/placeo-widget.css`:
- находит свой `<script>`, читает `data-key` и origin портала из `src`;
- строит DOM с **namespaced-классами `.plcw-*`**, подключает CSS портала со **scoped-ресетом** под `.plcw-root` — стили не текут в чужой сайт и не ломаются от него;
- резиновый (100% контейнера), цвета/шрифт из конфига → CSS-переменные;
- лента выбранных категорий + кнопка «Подать объявление»;
- **клик по карточке → модалка просмотра** (галерея, цена, характеристики, «Показать телефон» по `/widget/api/ad/{id}/phone`, «Открыть на Placeo»); Ctrl/⌘/средний клик — открыть на портале в новой вкладке.

## Авторизация подающего — внутри виджета

Объявление принадлежит **самому авторизованному посетителю** (его аккаунт Placeo), помечается `widget_id`. Вход открывается в защищённом popup на домене Placeo (`/widget/auth`), который отдаёт Bearer-токен (**Sanctum**) в `window.opener` через `postMessage`; токен хранится в `localStorage` хоста. Посетитель не уходит на сам сайт-витрину:
- **Телефон (основной)** — обратный flash call: `POST /widget/api/auth/phone/start` → `…/phone/verify` → токен (та же логика входа/регистрации по номеру, что на сайте, см. [auth.md](auth.md)).
- **Почта** — `POST /widget/api/auth/{login,register}` (+ сброс пароля кодом).
- **Яндекс — popup + postMessage**: `…/widget/oauth/yandex` → callback отдаёт токен в `window.opener`. Нужен Redirect URI в Яндекс-приложении: `http://placeo.ru/widget/oauth/yandex/callback`.

## Публичный API (CORS, без CSRF)

`routes/widget.php` (подключён в `bootstrap/app.php` с `SubstituteBindings`, CORS — `config/cors.php` для `widget/api/*`). Контроллеры `app/Http/Controllers/Widget/`:

| Маршрут | Назначение |
|---|---|
| `GET /widget/api/{widget}/config` | конфиг + категории формы |
| `GET /widget/api/{widget}/ads` | лента (с `only_own`) + **пользовательские фильтры**: `category_id`, `price_min`/`price_max`, `q`, `sort` (`price_asc`/`price_desc`). Каждый применяется только если включён в конфиге; `category_id` пересекается с разрешёнными ветками (чужое не «протекает»). |
| `POST /widget/api/{widget}/ads` (`auth:sanctum`) | подача (владелец = посетитель, `widget_id`) |
| `GET /widget/api/ad/{ad}` / `…/phone` | детали для модалки |
| `POST /widget/api/auth/phone/{start,verify}` | вход по телефону (flash call) → токен |
| `POST /widget/api/auth/{register,login}` | вход по почте → токен (throttle) |
| `GET /widget/oauth/yandex` / `…/callback` | OAuth popup |

`{widget}` биндится по `public_key`.

## Кабинет

`app/Http/Controllers/Cabinet/WidgetController.php` + `resources/views/cabinet/widgets/*`:
- CRUD виджетов; настройки: цвета (фон/карточки/кнопки/текст), размер шрифта, **категории** (Alpine `catPicker`), `allow_posting`, `only_own`;
- **фильтры для посетителей** (блок «Фильтры для посетителей»): `show_category_filter` + `category_filter_style` (`tabs_top` | `tabs_left` | `select`), `show_price_filter`, `show_search`, `show_sort`. По умолчанию категории (`tabs_top`) и цена включены; поиск и сортировка — выключены;
- генерация сниппета + **живой предпросмотр** (встроенный реальный загрузчик).

## Фильтры ленты

Общий слой применения цены/поиска/сортировки — `App\Support\AdFilters` (переиспользуется и главной лентой `FeedController`, и widget-API). Меню категорий для фильтра берётся из того же `GET /widget/api/{key}/categories` (плоский верхний уровень: настроенные ветки, либо все корневые). Клиентские фильтры реализованы в обоих фронтах: загрузчик `resources/widget/placeo-widget.js` (`buildFilterBar`, стили `.plcw-filters/.plcw-tabs/.plcw-two-col`) и Alpine `miniApp` (`filterQuery()`, `applyFilters()`).

## Модель

`app/Models/Widget.php`: `public_key` (генерится в `booted()`), `config(json)`, хелперы `cfg()`, `categoryIds()`, `defaultConfig()`. У `ads` — FK `widget_id`.

Объявления из виджетов видны на портале **как обычные**, без пометок.

## Проверка

Тестовая «чужая» страница: `D:\TEMP\widget-test-site\index.html`
```bash
php -S 127.0.0.1:9000 -t D:\TEMP\widget-test-site
# открыть http://127.0.0.1:9000/
```
