Commit Graph

1988 Commits

Author SHA1 Message Date
Egor
0b53e0e35f Merge PR #495: приоритет админского display_name/description над локалью на балансе 2026-07-19 16:34:40 +03:00
Egor
424d77455e Merge PR #493: состав промокода чекбоксами — баланс / дни / промогруппа 2026-07-19 16:29:08 +03:00
Egor
10b58cdc1f Merge PR #492: кнопка «Отправить сообщение» в карточке пользователя 2026-07-19 16:26:15 +03:00
Egor
812f73dd07 Merge pull request #498 from BEDOLAGA-DEV/main
w
2026-07-19 16:24:28 +03:00
Fringg
61d3169e7d Merge PR #491: vitest-чек синхронности en/ru локалей + запуск тестов в CI 2026-07-19 16:23:26 +03:00
Fringg
1fddd9473c test(i18n): опустошить allowlist после мержа #489 + перевод realtimeTitle
Поверх #491: #489 уже в dev — записи resetPassword.*/expiredDate_trial и
placeholder-исключение serverAccess из allowlist удалены (по инструкции
самого теста); последняя дыра admin.remnawave.traffic.realtimeTitle закрыта
переводом — проверка синхронности теперь строгая, без известных исключений
2026-07-19 16:23:17 +03:00
Fringg
b771152bb2 Merge branch 'test/locale-sync' into test-merge-491 2026-07-19 16:22:02 +03:00
Egor
e84ef0553e Merge PR #490: форматирование дат и чисел локалью выбранного языка 2026-07-19 16:18:50 +03:00
Egor
80ff53a036 Merge PR #489: недостающие английские переводы resetPassword и expiredDate_trial 2026-07-19 16:16:36 +03:00
smediainfo
112e09bc6d fix(payment-methods): admin display_name/description override locale on balance
White-label (per-method display_name + description in admin) was defeated on
balance/top-up pages: render used `translatedName || method.name` and
`translatedDesc || method.description`, so the bundled locale always won and
the admin override was ignored.

- Flip priority: admin-configured value first, locale as fallback
  (TopUpMethodSelect, Balance)
- Add missing Description field to payment-method edit page
  (mirrors Display name: description / reset_description)
- Add description to admin PaymentMethodConfig type
- Add description/descriptionHint i18n keys (ru, en)
2026-07-16 09:55:08 +03:00
Case211
9ce838bade feat(promocodes): состав промокода чекбоксами — баланс / дни / промогруппа
Форма создания промокода переведена с жёсткого выбора одного типа на режим
«Набор бонусов»: галочками выбирается, что входит в промокод — бонус на
баланс, дни подписки, промогруппа — в любой комбинации. Тип для бэкенда
выводится из выбора (balance / subscription_days / balance_and_days /
promo_group), промогруппа едет через promo_group_id при любом типе (бэкенд
назначает её при активации независимо от типа).

Триал и процентная скидка остаются отдельными режимами: скидка
переиспользует те же колонки с другой семантикой (процент/часы) и не
комбинируется без миграции.

- список и статистика: лейбл/цвет нового типа balance_and_days, у составных
  кодов видны оба значения (+руб и +дни)
- edit-режим восстанавливает чекбоксы из типа и promo_group_id
- унифицирована валидация формы (isValid = список ошибок пуст; для скидки
  0 часов = «до первой покупки» — разрешено, как и было в isValid)
- локали ru/en/fa/zh

Парный PR бэкенда: remnawave-bedolaga-telegram-bot (тип BALANCE_AND_DAYS,
порядок эффектов дни→баланс, валидации, бот-визард).
2026-07-14 08:52:06 +05:00
Case211
2f743d82db feat(users): кнопка «Отправить сообщение» в карточке пользователя
Паритет с бот-кнопкой «✉️ Отправить сообщение»: в блоке действий карточки
юзера появляется кнопка (RBAC users:send_message), открывающая модалку с
текстом (HTML, лимит 4096) — бот отправляет юзеру прямое сообщение через
POST /admin/users/{id}/send-message.

Для email-only юзеров без telegram_id кнопка задизейблена с подсказкой;
коды ошибок бэкенда (no_telegram_id / forbidden / bad_request) маппятся
в честные локализованные сообщения. Локали ru/en/fa/zh.
2026-07-14 07:28:33 +05:00
kewldan
2b5152608c test(i18n): vitest-чек синхронности en/ru локалей + запуск тестов в CI 2026-07-14 00:10:59 +03:00
kewldan
81fd96c2a7 fix(i18n): форматировать даты и числа локалью выбранного языка, а не браузера 2026-07-14 00:04:45 +03:00
kewldan
dd50d648d0 fix(i18n): добавить недостающие английские переводы resetPassword и expiredDate_trial 2026-07-13 23:37:07 +03:00
Egor
d7ea3c0635 Merge pull request #488 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.61.0
2026-07-13 21:55:21 +03:00
github-actions[bot]
c234100a4b chore(main): release 1.61.0 2026-07-13 18:54:59 +00:00
Egor
44369510bf Merge pull request #487 from BEDOLAGA-DEV/dev
Dev
2026-07-13 21:54:28 +03:00
Egor
9f909b0f2c Merge PR #486: fallback на покупку при переключении с бесплатного тарифа
fix(subscription): fall back to purchase flow for free-tariff switches
2026-07-13 15:36:37 +03:00
Egor
a47e9ab699 Merge PR #485: слушать IPv6 в nginx — фикс ложного unhealthy
fix(docker): слушать IPv6 в nginx — healthcheck на localhost уводил контейнер в unhealthy
2026-07-13 15:35:01 +03:00
Egor
eeb715c095 Merge PR #484: поле icon_custom_emoji_id у кастомных кнопок рассылки
feat(broadcasts): поле icon_custom_emoji_id у кастомных кнопок рассылки
2026-07-13 15:13:22 +03:00
Egor
478a693019 Merge PR #483: сдвиг страницы, мигание переключателя языка и перезапуск фона при SPA-навигации
fix(ui): сдвиг страницы, мигание переключателя языка и перезапуск фона при SPA-навигации
2026-07-13 15:01:12 +03:00
Egor
28ce17cd99 Merge PR #482: подключить vitest и покрыть утилиты юнит-тестами
test: подключить vitest и покрыть утилиты юнит-тестами
2026-07-13 14:45:40 +03:00
Chara-Freedom
255777e7d1 fix(subscription): fall back to purchase flow for free-tariff switches
The bot backend now rejects prorated tariff switches from a free (0RUB)
tariff with 400 free_tariff_cannot_switch + use_purchase_flow=true
(TARIFF_SWITCH_RESET_FREE_DAYS: the free remainder must reset, not be
prorated over hundreds of accumulated days and carried to a paid
tariff). Handle it like the existing trial_cannot_switch contract:

- SwitchTariffSheet: accept free_tariff_cannot_switch in
  shouldUsePurchaseFlow so the sheet hands off to the purchase form
- TariffPickerGrid: new subscription_on_free_tariff purchase-options
  flag disables the switch CTA up front and shows purchase instead

Companion to BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot#3056.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 05:36:12 +03:00
Fringg
f9ee16a2e6 feat(admin): бейдж «Команда» для команд бота в таймлайне активности
Команды (/start и др.) приходят типом button_click с подтипом command —
добавлен перевод бейджа во всех 4 локалях.
2026-07-13 04:19:32 +03:00
Fringg
5fc8e7f97a feat(admin): клики кнопок бота и действия в кабинете во вкладке «Активность»
Два новых типа записей таймлайна: button_click (нажатия кнопок в боте,
с текстом кнопки) и cabinet_action (мутационные действия юзера в
кабинете, метод + нормализованный путь). Чип-фильтр «Клики», иконки
BotIcon/BoltIcon, переводы во всех 4 локалях.

biome check, type-check и build проходят (pre-commit пропущен: biome
не установлен локально).
2026-07-13 03:46:53 +03:00
Fringg
7547b49d7a feat(admin): вкладка «Активность» в карточке пользователя
Единый таймлайн действий юзера в боте и кабинете
(GET /cabinet/admin/users/{id}/activity): вертикальная лента с
иконками-кружками по типу записи, бейджи подтипов, суммы со знаком
(+зелёный / −красный), относительное время (абсолютное — в title),
чипы-фильтры по категориям (платежи, события, промо, тикеты, подарки,
рефералы, входы), счётчик StatCard и постраничная догрузка по house
load-more паттерну. Компонент самодостаточный (ActivityTab), как
TicketsTab. Переводы во всех 4 локалях; время переиспользует
admin.auditLog.time.*.

biome check, type-check и build проходят (pre-commit пропущен: biome
не установлен локально — бинарь есть только в CI).
2026-07-13 03:34:07 +03:00
kewldan
ce4ce046ba fix(docker): слушать IPv6 в nginx — healthcheck на localhost уводил контейнер в unhealthy 2026-07-13 03:01:55 +03:00
Fringg
c920330fa6 feat(navigation): startapp=trial → дашборд (оплата платного триала из rich-меню)
Кнопка «Активировать триал» в rich-меню бота при платном триале ведёт в
миниапп: startapp=trial открывает дашборд, где TrialOfferCard показывает
цену и flow оплаты с баланса.

biome check, type-check и build проходят (pre-commit пропущен: biome не
установлен локально — бинарь есть только в CI).
2026-07-13 02:40:12 +03:00
kewldan
b4b4bca2cb feat(broadcasts): поле icon_custom_emoji_id для кастомных кнопок рассылки 2026-07-13 02:34:46 +03:00
Fringg
2eaea75fd9 feat(navigation): роутинг startapp-диплинков rich-меню бота
StartParamNavigator обобщён до таблицы маршрутов: помимо admin_ticket_<id>
теперь разбираются renew_<id> → /subscriptions/<id>/renew и subscriptions →
/subscriptions — их шлют ссылки «Продлить» у истёкших подписок в rich-меню
бота (t.me/<bot>/<app>?startapp=…). Доступ по-прежнему ограничен гвардами
самих маршрутов.

biome check, type-check и build проходят (pre-commit пропущен: biome не
установлен локально как зависимость — бинарь есть только в CI).
2026-07-13 02:12:57 +03:00
kewldan
1ea9b99ab5 fix(ui): не перезапускать анимированный фон при смене страницы 2026-07-13 01:52:36 +03:00
kewldan
a7ff8a6b09 fix(header): кэшировать список языков — переключатель не мигает при смене страницы 2026-07-13 01:46:24 +03:00
kewldan
b40f939e91 fix(ui): фиксированная ширина 100vw — страницы не сдвигаются при появлении скроллбара 2026-07-13 01:46:22 +03:00
kewldan
42788e842d test: подключить vitest и покрыть утилиты юнит-тестами 2026-07-13 01:09:33 +03:00
Egor
b7b103e6e2 Merge pull request #481 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.60.0
2026-07-13 00:06:07 +03:00
github-actions[bot]
5df992c587 chore(main): release 1.60.0 2026-07-12 21:05:10 +00:00
Egor
cad7214207 Merge pull request #480 from BEDOLAGA-DEV/dev
Dev
2026-07-13 00:04:41 +03:00
Fringg
758a645dff Merge PR #479: счётчик символов и предупреждение о разбивке длинных текстов
Парный к боту #3049. В /admin/legal-pages под полем политики/оферты/правил:
постоянный счётчик символов + предупреждение с оценкой числа сообщений бота
(estimateTelegramParts зеркалит split_telegram_text, порог 3500). Скрыто там,
где документ в боте не показывается (recurrent-payments, web-only); счётчик
остаётся. Переводы ru/en с плюральными формами, прочие локали — en fallback.

Валидация: JSON-ключи, tsc, biome lint/format, build — чисто.
2026-07-12 23:57:05 +03:00
Fringg
63b19645e6 Merge PR #478: stagger-анимация скрывала контент, пришедший из API после mount
framer-motion stagger раздаёт детям команду показаться один раз при монтировании
родителя; дети, смонтированные позже (когда resolve-ится react-query), оставались
в initial (opacity:0) — пустые страницы после F5/прямого захода.

- ConnectedAccounts, SavedCards: key={isLoading?...} перемонтирует контейнер
  после загрузки → stagger проигрывается со всеми детьми;
- Balance (2), Profile, Support: явные initial/animate на условных staggerItem —
  элемент самоанимируется при mount, вне оркестрации родителя;
- Wheel: исправлены несуществующие варианты hidden/show → initial/animate
  (stagger истории спинов молча не работал).

Валидация на смердженном дереве: tsc, biome lint/format, build — чисто.
Правки #477 и #478 в общих файлах (ConnectedAccounts/Profile) сосуществуют.
2026-07-12 23:45:08 +03:00
Fringg
0e6f80c090 Merge PR #477: нормализовать detail из ответов API перед рендером (краш на 422)
FastAPI/Pydantic на 422 кладёт в detail массив объектов {type,loc,msg,...};
компоненты рендерили его в JSX как есть → React падал в ErrorBoundary
(Objects are not valid as a React child), пользователь не видел причину.

Все такие обработчики (Login, Profile, ConnectedAccounts, admin AnalyticsTab
и MenuEditorTab) переведены на существующий getApiErrorMessage — строковый
detail возвращается как есть (ветки .includes работают), Pydantic-массив
разворачивается в строку field: msg; ..., любой формат безопасен для рендера.

Валидация на смердженном дереве: tsc, biome lint/format, build — чисто.
2026-07-12 23:22:26 +03:00
kewldan
93cfca46dc feat(admin): предупреждение о разбивке длинных текстов системных страниц в боте
Кабинет, в отличие от админки бота (лимит 4000 символов), позволяет
сохранять юридические тексты любой длины — и до парного фикса в боте
(BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot#3049) текст длиннее
~4096 символов ронял показ правил и политики при /start
(MESSAGE_TOO_LONG).

Теперь под текстовым полем политики, оферты и правил постоянно
показывается счётчик символов, а для текстов длиннее одного сообщения —
предупреждение с оценкой, на сколько сообщений бот разобьёт текст.
Оценка зеркалит жадную упаковку абзацев из split_telegram_text бота
(порог 3500 символов на кусок).

Предупреждение не показывается там, где оно неуместно:
- «Рекуррентные платежи» — документ не отображается в боте вовсе;
- режим отображения «Только веб» — бот документ не показывает.

Переводы добавлены в ru/en (с плюральными формами); остальные локали
используют английский fallback через defaultValue.
2026-07-12 19:41:03 +03:00
kewldan
adaaa23da3 fix(ui): контент, пришедший из API после mount, оставался невидимым (stagger)
Оркестрация staggerContainer/staggerItem у framer-motion раздаёт детям
команду «показаться» один раз — при анимации родителя на mount. Дети,
смонтированные позже (когда resolve'ится запрос react-query), в
оркестрацию не попадают и навсегда остаются в initial (opacity: 0, y: 8).

Симптом: после жёсткой перезагрузки (F5) на странице виден только
заголовок — карточки есть в DOM, но прозрачны. При SPA-навигации кэш
react-query тёплый, дети монтируются вместе с родителем — поэтому баг
проявлялся только при холодном заходе и маскировался на проде (нет
StrictMode, same-origin API, тёплый кэш почти всегда выигрывают гонку
с ~300-мс окном анимации).

Два способа фикса в зависимости от формы страницы:

1. key={isLoading ? 'loading' : 'ready'} на контейнере — перемонтирование
   после загрузки, stagger проигрывается заново уже со всеми детьми.
   Для страниц, где весь контент асинхронный (каскад сохраняется):
   - ConnectedAccounts — карточки провайдеров
   - SavedCards — список сохранённых карт

2. Явные initial="initial" animate="animate" на условном staggerItem —
   элемент анимирует себя сам, не дожидаясь оркестрации родителя. Для
   страниц, где асинхронны только 1-2 опциональные карточки (не
   перезапускает анимацию всей страницы на каждый resolve):
   - Balance — «Способы пополнения» и ссылка «Сохранённые карты»
   - Support — карточка «Написать нам» (support_type='both')
   - Profile — реферальный виджет

Попутно: в Wheel у контейнера истории спинов стояли несуществующие
варианты initial="hidden"/animate="show" (у staggerContainer/staggerItem
ключи — initial/animate/exit), из-за чего stagger там молча не работал
вовсе; имена исправлены.

Проверено и не требует правок: TopUpAmount (early-return до загрузки
методов), TopUpMethodSelect и MergeAccounts (динамический контент —
обычный HTML внутри всегда смонтированных staggerItem), вложенный
контейнер транзакций в Balance (самоанимирующийся).
2026-07-12 19:08:09 +03:00
kewldan
9b972931cf fix(ui): нормализовать detail из ответов API перед рендером — краш на 422
Что: все обработчики ошибок, которые клали сырой response.data.detail в
состояние и затем в JSX, переведены на существующий хелпер
getApiErrorMessage (src/utils/api-error.ts):

- Login.tsx — Telegram-auth, вход/регистрация по email, восстановление пароля
- Profile.tsx — смена email (запрос и подтверждение кода), повторная
  отправка письма верификации
- ConnectedAccounts.tsx — привязка email к аккаунту
- admin/AnalyticsTab.tsx, admin/MenuEditorTab.tsx — сохранение настроек

Почему: на невалидный по схеме запрос FastAPI/Pydantic отвечает 422, где
detail — не строка, а массив объектов {type, loc, msg, input, ctx}.
Компоненты рендерили его как есть, React падал с «Objects are not valid
as a React child», и вся страница (включая логин) уходила в ErrorBoundary
«Something went wrong» — пользователь не видел причину отказа вообще.

Зачем: getApiErrorMessage сводит Pydantic-массив к строке
«field: message; ...», поэтому вместо краша форма показывает настоящий
текст ошибки валидации. Ветвления по detail.includes(...) сохранены и
работают по нормализованной строке — поведение прежнее для строковых
detail, но безопасное для любой формы ответа.
2026-07-12 18:37:04 +03:00
Fringg
9f840a9a47 Merge PR #474: миграция тулинга с ESLint + Prettier на Biome
Замена ESLint + Prettier (9 dev-зависимостей) на Biome (одна): линт ~10×,
формат-чек ~49× быстрее. Паритет форматирования (single quotes/semi/width
100/trailing/arrowParens/jsxDouble) и Telegram WebView-гвардов (перенесены
GritQL-плагином). Имена npm-скриптов lint/format:check сохранены — CI не тронут.

Ревью-фиксы поверх PR (восстановление точного паритета):
- grit-исключение сужено до якорного src/platform/ (было bare-substring
  "platform", щадившее любой файл с этим словом в имени)
- вернул *.min.js/*.min.css в biome ignore (были в .prettierignore)
- вернул *.css в lint-staged pre-commit
- поправил устаревшую метку CI-шага
2026-07-12 01:36:15 +03:00
Fringg
fd85ba1ae1 chore(tooling): восстановить точный паритет с eslint/prettier
- telegram-webview-guards.grit: исключение сузил с подстроки "platform"
  до якорного "src/platform/" — прежний eslint-override действовал ровно на
  src/platform/**, а bare-substring щадил бы любой файл с "platform" в имени
  (проверено: src/platform всё ещё exempt, гвард срабатывает в обычных файлах)
- biome.json: вернул исключения *.min.js/*.min.css из .prettierignore
- lint-staged: вернул форматирование *.css в pre-commit (biome format --write) —
  иначе CI-формат-чек ловил бы css, который хук не трогал
- lint.yml: метка шага "Run ESLint" → "Run linter" (выполняет biome)
2026-07-12 01:35:44 +03:00
kewldan
0abc2f30ae chore(tooling): точечный CSS-линт вместо полного отключения
По итогам разбора Biome 2.5 x Tailwind v3: noUnknownAtRules и
noUnknownFunction не выключаем, а используем их опцию ignore
(tailwind; theme/screen) - детект опечаток в at-rules и функциях
сохраняется. noDuplicateProperties off: наши дубли - прогрессивные
фолбэки (100dvh -> var(--tg-viewport-stable-height)), опции
"разрешить фолбэки" нет (biomejs/biome#6051). noImportantStyles off:
все 31 !important осознанные. @tailwind в ignore из-за
biomejs/biome#7899 (tailwindDirectives их пока не покрывает).
2026-07-12 00:44:35 +03:00
Fringg
f1b87a73b3 Merge PR #473: раздел оптовых купонов + публичная страница активации
UI для фичи «Купоны» (backend — remnawave-bot #3044):
- админ-раздел /admin/coupons: список партий со статистикой, форма создания
  с готовыми ссылками (копировать/скачать .txt), карточка партии с экспортом
  и отзывом непогашенных за PermissionGate coupons:edit
- публичная /coupon/:token: партнёр видит тариф/срок, кнопки активации в боте
  и (для залогиненных) в кабинете с маппингом машинных кодов ошибок
- локали ru/en; zh/fa через ru-фолбэк

Ревью-фиксы поверх PR:
- единый formatShortDate вместо 3 копий formatDate + инлайн localeMap
- статистика списка показывается только для одной страницы (суммы по
  странице иначе противоречат глобальному total)
- retry:false на публичном статусе (404 — штатный ответ)
2026-07-12 00:34:59 +03:00
Fringg
b011b55f04 refactor(coupons): единый formatShortDate + честная статистика списка + retry
- formatShortDate вынесен в utils/format вместо трёх копий formatDate +
  инлайн localeMap (AdminCoupons/AdminCouponDetail/CouponStatus)
- список партий: агрегатные карточки Активны/Погашено/Отозвано считались
  только по текущей странице и противоречили глобальному total «Партий» —
  показываем их лишь когда весь набор влезает на одну страницу
- CouponStatus: retry:false на публичном статусе (404 — ожидаемый ответ на
  невалидный/погашенный токен, ретрай зря бьёт rate-limited эндпоинт)
2026-07-12 00:34:41 +03:00
kewldan
c03182e6cf chore(tooling): biome 2.3+ — нативный tailwind-парсер и lineEnding auto
По фичам Biome v2.3: css.parser.tailwindDirectives снимает исключение
CSS из Biome (globals.css снова под формат-контролем; линт CSS выключен
override-ом — tailwind-функции theme() и пр. правила пока не знают),
lineEnding auto повторяет поведение prettier endOfLine: auto (CRLF в
Windows-чекаутах, LF на CI). Однократный формат-дифф globals.css
45+/45- — расхождения CSS-форматтера Biome с Prettier.
2026-07-12 00:28:42 +03:00