feat(gift): QR подарка и сканирование в кабинете

Получателю раньше оставалось только скопировать ссылку или код руками.

- QR на экране готового подарка: кодируется bot-ссылка (активация в боте —
  основной путь), при отсутствии username бота — ссылка кабинета. Использован
  уже имеющийся qrcode.react, на белой подложке ради контраста в тёмной теме.
- Кнопка «Отсканировать QR» в форме активации. В Telegram используется
  НАТИВНЫЙ сканер (в WebView камера через getUserMedia работает ненадёжно —
  ровно то, из-за чего скан «в тг» и был проблемой), в вебе — html5-qrcode с
  CDN, с фолбэком на фронтальную камеру и понятной ошибкой, если камеры нет.
- src/utils/qrScanner.ts: логика сканера вынесена из TvQuickConnect, чтобы её
  мог переиспользовать любой экран. parseGiftCode понимает все три способа
  распространения подарка (deep-link бота, ссылка кабинета, голый код с
  префиксом GIFT- и без) и ОТКЛОНЯЕТ посторонние QR — иначе сканер подставил
  бы в поле мусор с любой ссылки или Wi-Fi-кода.
- Веб-сканер гасится при уходе с вкладки и размонтировании, иначе индикатор
  камеры остаётся гореть.

Локали ru/en/zh/fa, +4 теста на парсер.
This commit is contained in:
Fringg
2026-07-29 22:11:04 +03:00
parent 390da33914
commit 0e4dc43335
8 changed files with 281 additions and 5 deletions

View File

@@ -5504,7 +5504,15 @@
"shareModalActivateVia": "Activate via bot:",
"shareModalActivateViaCabinet": "Or via website:",
"copyMessage": "Copy message",
"shareToastCopied": "Message copied"
"shareToastCopied": "Message copied",
"qrHint": "Scan to activate the gift",
"scanButton": "📷 Scan QR",
"scanInProgress": "Scanning…",
"scanCancel": "Stop scanning",
"scanDescription": "Point the camera at the gift QR code",
"scanNotRecognized": "No gift code in this QR",
"scanError": "Could not scan",
"scanNoCamera": "Camera unavailable — enter the code manually"
},
"news": {
"title": "News & Updates",

View File

@@ -5012,7 +5012,15 @@
"tabActivate": "فعال‌سازی",
"tabBuy": "خرید",
"tabMyGifts": "هدایای من",
"unlimitedTraffic": "ترافیک نامحدود"
"unlimitedTraffic": "ترافیک نامحدود",
"qrHint": "برای فعال‌سازی هدیه اسکن کنید",
"scanButton": "📷 اسکن QR",
"scanInProgress": "در حال اسکن…",
"scanCancel": "توقف اسکن",
"scanDescription": "دوربین را روی کد QR هدیه بگیرید",
"scanNotRecognized": "در این QR کد هدیه‌ای نیست",
"scanError": "اسکن ناموفق بود",
"scanNoCamera": "دوربین در دسترس نیست — کد را دستی وارد کنید"
},
"news": {
"title": "اخبار و به‌روزرسانی‌ها",

View File

@@ -6058,7 +6058,15 @@
"shareModalActivateVia": "Активировать через бота:",
"shareModalActivateViaCabinet": "Или через сайт:",
"copyMessage": "Скопировать сообщение",
"shareToastCopied": "Сообщение скопировано"
"shareToastCopied": "Сообщение скопировано",
"qrHint": "Отсканируйте, чтобы активировать подарок",
"scanButton": "📷 Отсканировать QR",
"scanInProgress": "Сканирование…",
"scanCancel": "Остановить сканирование",
"scanDescription": "Наведите камеру на QR-код подарка",
"scanNotRecognized": "В QR-коде нет кода подарка",
"scanError": "Не удалось отсканировать",
"scanNoCamera": "Камера недоступна — введите код вручную"
},
"news": {
"title": "Новости и обновления",

View File

@@ -5011,7 +5011,15 @@
"tabActivate": "激活",
"tabBuy": "购买",
"tabMyGifts": "我的礼物",
"unlimitedTraffic": "无限流量"
"unlimitedTraffic": "无限流量",
"qrHint": "扫描以激活礼物",
"scanButton": "📷 扫描二维码",
"scanInProgress": "扫描中…",
"scanCancel": "停止扫描",
"scanDescription": "将摄像头对准礼物二维码",
"scanNotRecognized": "该二维码中没有礼物码",
"scanError": "扫描失败",
"scanNoCamera": "摄像头不可用 — 请手动输入代码"
},
"news": {
"title": "新闻与更新",