diff --git a/src/api/promocodes.ts b/src/api/promocodes.ts index e459f1e..e162937 100644 --- a/src/api/promocodes.ts +++ b/src/api/promocodes.ts @@ -17,6 +17,8 @@ export interface PromoCode { balance_bonus_kopeks: number; balance_bonus_rubles: number; subscription_days: number; + /** Гигабайты к подписке — третья составляющая набора бонусов. */ + traffic_gb: number; max_uses: number; current_uses: number; uses_left: number; @@ -60,6 +62,7 @@ export interface PromoCodeCreateRequest { type: PromoCodeType; balance_bonus_kopeks?: number; subscription_days?: number; + traffic_gb?: number; max_uses?: number; valid_from?: string; valid_until?: string | null; @@ -74,6 +77,7 @@ export interface PromoCodeUpdateRequest { type?: PromoCodeType; balance_bonus_kopeks?: number; subscription_days?: number; + traffic_gb?: number; max_uses?: number; valid_from?: string; valid_until?: string | null; diff --git a/src/locales/en.json b/src/locales/en.json index b07b36d..2456821 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -3300,7 +3300,10 @@ "save": "Save", "defaultTrialTariff": "Default trial tariff", "selectTariff": "Select tariff", - "tariff": "Tariff" + "tariff": "Tariff", + "includeTraffic": "Traffic", + "trafficAmount": "Traffic amount", + "gb": "GB" }, "stats": { "title": "Promo code statistics", @@ -3374,7 +3377,8 @@ "daysRequired": "Number of days must be greater than 0", "groupRequired": "Select a discount group", "discountPercentInvalid": "Discount percent must be between 1 and 100", - "discountHoursRequired": "Specify the discount validity period in hours" + "discountHoursRequired": "Specify the discount validity period in hours", + "trafficRequired": "Traffic amount must be greater than 0" } }, "promoGroups": { diff --git a/src/locales/fa.json b/src/locales/fa.json index cbbb785..bcd755b 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -2810,7 +2810,10 @@ "save": "ذخیره", "defaultTrialTariff": "تعرفه پیش‌فرض آزمایشی", "selectTariff": "تعرفه را انتخاب کنید", - "tariff": "تعرفه" + "tariff": "تعرفه", + "includeTraffic": "ترافیک", + "trafficAmount": "حجم ترافیک", + "gb": "گیگابایت" }, "stats": { "title": "آمار کد تخفیف", @@ -2882,7 +2885,8 @@ "daysRequired": "تعداد روزها باید بیشتر از 0 باشد", "groupRequired": "یک گروه تخفیف انتخاب کنید", "discountPercentInvalid": "درصد تخفیف باید بین 1 تا 100 باشد", - "discountHoursRequired": "مدت اعتبار تخفیف را به ساعت مشخص کنید" + "discountHoursRequired": "مدت اعتبار تخفیف را به ساعت مشخص کنید", + "trafficRequired": "حجم ترافیک باید بیشتر از ۰ باشد" } }, "promoGroups": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 080e88f..d6e166d 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -3694,7 +3694,10 @@ "save": "Сохранить", "defaultTrialTariff": "Тариф триала по умолчанию", "selectTariff": "Выберите тариф", - "tariff": "Тариф" + "tariff": "Тариф", + "includeTraffic": "Трафик", + "trafficAmount": "Объём трафика", + "gb": "ГБ" }, "stats": { "title": "Статистика промокода", @@ -3770,7 +3773,8 @@ "daysRequired": "Количество дней должно быть больше 0", "groupRequired": "Выберите группу скидок", "discountPercentInvalid": "Процент скидки должен быть от 1 до 100", - "discountHoursRequired": "Укажите время действия скидки в часах" + "discountHoursRequired": "Укажите время действия скидки в часах", + "trafficRequired": "Объём трафика должен быть больше 0" } }, "promoGroups": { diff --git a/src/locales/zh.json b/src/locales/zh.json index 49b202d..db3ca90 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -2809,7 +2809,10 @@ "save": "保存", "defaultTrialTariff": "默认试用套餐", "selectTariff": "选择套餐", - "tariff": "套餐" + "tariff": "套餐", + "includeTraffic": "流量", + "trafficAmount": "流量额度", + "gb": "GB" }, "stats": { "title": "促销码统计", @@ -2881,7 +2884,8 @@ "daysRequired": "天数必须大于0", "groupRequired": "请选择折扣组", "discountPercentInvalid": "折扣百分比必须在1到100之间", - "discountHoursRequired": "请指定折扣有效期(小时)" + "discountHoursRequired": "请指定折扣有效期(小时)", + "trafficRequired": "流量额度必须大于 0" } }, "promoGroups": { diff --git a/src/pages/AdminPromocodeCreate.tsx b/src/pages/AdminPromocodeCreate.tsx index f9975c3..f24c39a 100644 --- a/src/pages/AdminPromocodeCreate.tsx +++ b/src/pages/AdminPromocodeCreate.tsx @@ -44,6 +44,8 @@ export default function AdminPromocodeCreate() { const [includeBalance, setIncludeBalance] = useState(true); const [includeDays, setIncludeDays] = useState(false); const [includeGroup, setIncludeGroup] = useState(false); + const [includeTraffic, setIncludeTraffic] = useState(false); + const [trafficGb, setTrafficGb] = useState(0); const [balanceBonusRubles, setBalanceBonusRubles] = useState(0); const [subscriptionDays, setSubscriptionDays] = useState(0); const [maxUses, setMaxUses] = useState(1); @@ -99,6 +101,8 @@ export default function AdminPromocodeCreate() { setBalanceBonusRubles(data.balance_bonus_rubles || 0); } setSubscriptionDays(data.subscription_days || 0); + setTrafficGb(data.traffic_gb || 0); + setIncludeTraffic((data.traffic_gb || 0) > 0); setMaxUses(data.max_uses || 1); setIsActive(data.is_active ?? true); setFirstPurchaseOnly(data.first_purchase_only || false); @@ -133,7 +137,7 @@ export default function AdminPromocodeCreate() { // едет через promo_group_id при любом типе (бэкенд применяет её независимо). const derivedType: PromoCodeType = mode === 'bonus_set' - ? includeBalance && includeDays + ? includeTraffic || (includeBalance && includeDays) ? 'balance_and_days' : includeBalance ? 'balance' @@ -148,6 +152,7 @@ export default function AdminPromocodeCreate() { const balanceValue = balanceBonusRubles === '' ? 0 : balanceBonusRubles; const daysValue = subscriptionDays === '' ? 0 : subscriptionDays; const maxUsesValue = maxUses === '' ? 0 : maxUses; + const trafficValue = trafficGb === '' ? 0 : trafficGb; const data: PromoCodeCreateRequest | PromoCodeUpdateRequest = { code: code.trim().toUpperCase(), @@ -164,6 +169,7 @@ export default function AdminPromocodeCreate() { (mode === 'bonus_set' && includeDays) ? daysValue : 0, + traffic_gb: mode === 'bonus_set' && includeTraffic ? trafficValue : 0, max_uses: maxUsesValue, is_active: isActive, first_purchase_only: firstPurchaseOnly, @@ -197,7 +203,7 @@ export default function AdminPromocodeCreate() { validationErrors.push('codeRequired'); } if (mode === 'bonus_set') { - if (!includeBalance && !includeDays && !includeGroup) { + if (!includeBalance && !includeDays && !includeGroup && !includeTraffic) { validationErrors.push('bonusSetEmpty'); } if (includeBalance && balanceValue <= 0) { @@ -209,6 +215,9 @@ export default function AdminPromocodeCreate() { if (includeGroup && !promoGroupId) { validationErrors.push('groupRequired'); } + if (includeTraffic && (trafficGb === '' || trafficGb <= 0)) { + validationErrors.push('trafficRequired'); + } } if (mode === 'trial_subscription' && daysValue <= 0) { validationErrors.push('daysRequired'); @@ -308,6 +317,7 @@ export default function AdminPromocodeCreate() { [ ['includeBalance', includeBalance, setIncludeBalance] as const, ['includeDays', includeDays, setIncludeDays] as const, + ['includeTraffic', includeTraffic, setIncludeTraffic] as const, ['includePromoGroup', includeGroup, setIncludeGroup] as const, ] as const ).map(([key, checked, setChecked]) => ( @@ -361,6 +371,28 @@ export default function AdminPromocodeCreate() { )} + {mode === 'bonus_set' && includeTraffic && ( +
+ +
+ + {t('admin.promocodes.form.gb')} +
+
+ )} + {(mode === 'trial_subscription' || (mode === 'bonus_set' && includeDays)) && (