Merge pull request #87 from BEDOLAGA-DEV/dev

Dev
This commit is contained in:
Egor
2026-01-23 03:46:57 +03:00
committed by GitHub
4 changed files with 50 additions and 12 deletions

View File

@@ -216,6 +216,7 @@
"pausedMessage": "Subscription paused", "pausedMessage": "Subscription paused",
"resumedMessage": "Subscription resumed", "resumedMessage": "Subscription resumed",
"dailyOnly": "Pause is only available for daily tariffs", "dailyOnly": "Pause is only available for daily tariffs",
"insufficientBalance": "Insufficient balance to resume subscription",
"pausedInfo": "Subscription paused", "pausedInfo": "Subscription paused",
"pausedDescription": "Charges stopped. Subscription will be active until", "pausedDescription": "Charges stopped. Subscription will be active until",
"nextCharge": "Until next charge", "nextCharge": "Until next charge",

View File

@@ -216,6 +216,7 @@
"pausedMessage": "Подписка приостановлена", "pausedMessage": "Подписка приостановлена",
"resumedMessage": "Подписка возобновлена", "resumedMessage": "Подписка возобновлена",
"dailyOnly": "Пауза доступна только для суточных тарифов", "dailyOnly": "Пауза доступна только для суточных тарифов",
"insufficientBalance": "Недостаточно средств для возобновления подписки",
"pausedInfo": "Подписка приостановлена", "pausedInfo": "Подписка приостановлена",
"pausedDescription": "Списания остановлены. Подписка будет действовать до", "pausedDescription": "Списания остановлены. Подписка будет действовать до",
"nextCharge": "До следующего списания", "nextCharge": "До следующего списания",

View File

@@ -136,7 +136,7 @@ function PeriodTariffModal({ tariff, servers, onSave, onClose, isLoading }: Peri
const [name, setName] = useState(tariff?.name || '') const [name, setName] = useState(tariff?.name || '')
const [description, setDescription] = useState(tariff?.description || '') const [description, setDescription] = useState(tariff?.description || '')
const [trafficLimitGb, setTrafficLimitGb] = useState(tariff?.traffic_limit_gb || 100) const [trafficLimitGb, setTrafficLimitGb] = useState(tariff?.traffic_limit_gb ?? 100)
const [deviceLimit, setDeviceLimit] = useState(tariff?.device_limit || 1) const [deviceLimit, setDeviceLimit] = useState(tariff?.device_limit || 1)
const [devicePriceKopeks, setDevicePriceKopeks] = useState(tariff?.device_price_kopeks || 0) const [devicePriceKopeks, setDevicePriceKopeks] = useState(tariff?.device_price_kopeks || 0)
const [maxDeviceLimit, setMaxDeviceLimit] = useState(tariff?.max_device_limit || 0) const [maxDeviceLimit, setMaxDeviceLimit] = useState(tariff?.max_device_limit || 0)
@@ -182,7 +182,7 @@ function PeriodTariffModal({ tariff, servers, onSave, onClose, isLoading }: Peri
device_price_kopeks: devicePriceKopeks > 0 ? devicePriceKopeks : undefined, device_price_kopeks: devicePriceKopeks > 0 ? devicePriceKopeks : undefined,
max_device_limit: maxDeviceLimit > 0 ? maxDeviceLimit : undefined, max_device_limit: maxDeviceLimit > 0 ? maxDeviceLimit : undefined,
tier_level: tierLevel, tier_level: tierLevel,
period_prices: periodPrices.filter(p => p.price_kopeks > 0), period_prices: periodPrices.filter(p => p.price_kopeks >= 0),
allowed_squads: selectedSquads, allowed_squads: selectedSquads,
traffic_topup_enabled: trafficTopupEnabled, traffic_topup_enabled: trafficTopupEnabled,
traffic_topup_packages: trafficTopupPackages, traffic_topup_packages: trafficTopupPackages,
@@ -757,7 +757,7 @@ function DailyTariffModal({ tariff, servers, onSave, onClose, isLoading }: Daily
const [name, setName] = useState(tariff?.name || '') const [name, setName] = useState(tariff?.name || '')
const [description, setDescription] = useState(tariff?.description || '') const [description, setDescription] = useState(tariff?.description || '')
const [trafficLimitGb, setTrafficLimitGb] = useState(tariff?.traffic_limit_gb || 100) const [trafficLimitGb, setTrafficLimitGb] = useState(tariff?.traffic_limit_gb ?? 100)
const [deviceLimit, setDeviceLimit] = useState(tariff?.device_limit || 1) const [deviceLimit, setDeviceLimit] = useState(tariff?.device_limit || 1)
const [devicePriceKopeks, setDevicePriceKopeks] = useState(tariff?.device_price_kopeks || 0) const [devicePriceKopeks, setDevicePriceKopeks] = useState(tariff?.device_price_kopeks || 0)
const [maxDeviceLimit, setMaxDeviceLimit] = useState(tariff?.max_device_limit || 0) const [maxDeviceLimit, setMaxDeviceLimit] = useState(tariff?.max_device_limit || 0)

View File

@@ -21,6 +21,20 @@ const getErrorMessage = (error: unknown): string => {
return 'Произошла ошибка' return 'Произошла ошибка'
} }
// Helper to extract insufficient balance error details
const getInsufficientBalanceError = (error: unknown): { required: number; balance: number } | null => {
if (error instanceof AxiosError) {
const detail = error.response?.data?.detail
if (typeof detail === 'object' && detail?.code === 'insufficient_balance') {
return {
required: detail.required || 0,
balance: detail.balance || 0,
}
}
}
return null
}
// Icons // Icons
const CopyIcon = () => ( const CopyIcon = () => (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
@@ -742,11 +756,26 @@ export default function Subscription() {
</div> </div>
{/* Pause mutation error */} {/* Pause mutation error */}
{pauseMutation.isError && ( {pauseMutation.isError && (() => {
<div className="mt-4 text-sm text-error-400 bg-error-500/10 px-4 py-3 rounded-lg text-center"> const balanceError = getInsufficientBalanceError(pauseMutation.error)
{getErrorMessage(pauseMutation.error)} if (balanceError) {
</div> const missingAmount = balanceError.required - balanceError.balance
)} return (
<div className="mt-4">
<InsufficientBalancePrompt
missingAmountKopeks={missingAmount}
message={t('subscription.pause.insufficientBalance')}
compact
/>
</div>
)
}
return (
<div className="mt-4 text-sm text-error-400 bg-error-500/10 px-4 py-3 rounded-lg text-center">
{getErrorMessage(pauseMutation.error)}
</div>
)
})()}
{/* Paused info or Next charge progress bar */} {/* Paused info or Next charge progress bar */}
{subscription.is_daily_paused ? ( {subscription.is_daily_paused ? (
@@ -2276,10 +2305,17 @@ export default function Subscription() {
<div className="text-sm text-success-400 text-center">{preview.discount_label}</div> <div className="text-sm text-success-400 text-center">{preview.discount_label}</div>
)} )}
{!preview.can_purchase && preview.status_message && ( {!preview.can_purchase && (
<div className="text-sm text-error-400 bg-error-500/10 px-4 py-3 rounded-lg text-center"> preview.missing_amount_kopeks > 0 ? (
{preview.status_message} <InsufficientBalancePrompt
</div> missingAmountKopeks={preview.missing_amount_kopeks}
compact
/>
) : preview.status_message ? (
<div className="text-sm text-error-400 bg-error-500/10 px-4 py-3 rounded-lg text-center">
{preview.status_message}
</div>
) : null
)} )}
</div> </div>
) : null} ) : null}