feat(coupons): раздел оптовых купонов + публичная страница активации

UI для купонов из BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot#3042
(API: PR #3044 бота):

- /admin/coupons — список партий с погашениями и offset-пагинацией;
- /admin/coupons/create — мастер партии (тариф, дни, количество,
  учётная оптовая цена, срок), после создания — экран со ссылками
  (копирование всех, скачивание .txt);
- /admin/coupons/:id — карточка со статистикой, экспорт актуальных
  ссылок, отзыв непогашенных за PermissionGate coupons:edit
  с подтверждением и тостом;
- /coupon/:token — публичная страница купона: тариф/срок, кнопка
  активации в боте, для залогиненных — активация прямо в кабинете
  с маппингом структурированных кодов ошибок {code, message};
- пункт меню в разделе тарифов (гейт coupons:read), лениво
  загружаемые роуты, локали ru/en (zh/fa падают на ru-фолбэк).
This commit is contained in:
kewldan
2026-07-11 23:52:13 +03:00
parent 20565b8f59
commit 799e986d84
9 changed files with 1324 additions and 0 deletions

View File

@@ -94,6 +94,10 @@ const AdminBroadcasts = lazyWithRetry(() => import('./pages/AdminBroadcasts'));
const AdminBroadcastCreate = lazyWithRetry(() => import('./pages/AdminBroadcastCreate'));
const AdminPromocodes = lazyWithRetry(() => import('./pages/AdminPromocodes'));
const AdminPromocodeCreate = lazyWithRetry(() => import('./pages/AdminPromocodeCreate'));
const AdminCoupons = lazyWithRetry(() => import('./pages/AdminCoupons'));
const AdminCouponCreate = lazyWithRetry(() => import('./pages/AdminCouponCreate'));
const AdminCouponDetail = lazyWithRetry(() => import('./pages/AdminCouponDetail'));
const CouponStatus = lazyWithRetry(() => import('./pages/CouponStatus'));
const AdminPromocodeStats = lazyWithRetry(() => import('./pages/AdminPromocodeStats'));
const AdminPromoGroups = lazyWithRetry(() => import('./pages/AdminPromoGroups'));
const AdminPromoGroupCreate = lazyWithRetry(() => import('./pages/AdminPromoGroupCreate'));
@@ -294,6 +298,14 @@ function App() {
</LazyPage>
}
/>
<Route
path="/coupon/:token"
element={
<LazyPage>
<CouponStatus />
</LazyPage>
}
/>
<Route
path="/buy/:slug"
element={
@@ -839,6 +851,36 @@ function App() {
</PermissionRoute>
}
/>
<Route
path="/admin/coupons"
element={
<PermissionRoute permission="coupons:read">
<LazyPage>
<AdminCoupons />
</LazyPage>
</PermissionRoute>
}
/>
<Route
path="/admin/coupons/create"
element={
<PermissionRoute permission="coupons:create">
<LazyPage>
<AdminCouponCreate />
</LazyPage>
</PermissionRoute>
}
/>
<Route
path="/admin/coupons/:id"
element={
<PermissionRoute permission="coupons:read">
<LazyPage>
<AdminCouponDetail />
</LazyPage>
</PermissionRoute>
}
/>
<Route
path="/admin/promocodes/:id/stats"
element={