diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 09a130d1..1a446032 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -9,10 +9,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database.crud.server_squad import get_server_squad_by_uuid -from app.database.crud.promo_group import get_auto_assign_promo_groups -from app.database.crud.transaction import get_user_total_spent_kopeks from app.database.crud.user import get_user_by_telegram_id -from app.database.models import PromoGroup, Subscription, Transaction, User +from app.database.models import Subscription, Transaction, User from app.services.remnawave_service import ( RemnaWaveConfigurationError, RemnaWaveService, @@ -28,7 +26,6 @@ from ..dependencies import get_db_session from ..schemas.miniapp import ( MiniAppConnectedServer, MiniAppDevice, - MiniAppAutoPromoGroupLevel, MiniAppPromoGroup, MiniAppSubscriptionRequest, MiniAppSubscriptionResponse, @@ -339,27 +336,6 @@ async def get_subscription_details( balance_currency = balance_currency.upper() promo_group = getattr(user, "promo_group", None) - total_spent_kopeks = await get_user_total_spent_kopeks(db, user.id) - auto_assign_groups = await get_auto_assign_promo_groups(db) - - auto_promo_levels: List[MiniAppAutoPromoGroupLevel] = [] - for group in auto_assign_groups: - threshold = group.auto_assign_total_spent_kopeks or 0 - if threshold <= 0: - continue - - auto_promo_levels.append( - MiniAppAutoPromoGroupLevel( - id=group.id, - name=group.name, - threshold_kopeks=threshold, - threshold_rubles=round(threshold / 100, 2), - threshold_label=settings.format_price(threshold), - is_reached=total_spent_kopeks >= threshold, - is_current=bool(promo_group and promo_group.id == group.id), - **_extract_promo_discounts(group), - ) - ) response_user = MiniAppSubscriptionUser( telegram_id=user.telegram_id, @@ -410,66 +386,11 @@ async def get_subscription_details( balance_rubles=round(user.balance_rubles, 2), balance_currency=balance_currency, transactions=[_serialize_transaction(tx) for tx in transactions], - promo_group=( - MiniAppPromoGroup( - id=promo_group.id, - name=promo_group.name, - **_extract_promo_discounts(promo_group), - ) - if promo_group - else None - ), - auto_assign_promo_groups=auto_promo_levels, - total_spent_kopeks=total_spent_kopeks, - total_spent_rubles=round(total_spent_kopeks / 100, 2), - total_spent_label=settings.format_price(total_spent_kopeks), + promo_group=MiniAppPromoGroup(id=promo_group.id, name=promo_group.name) + if promo_group + else None, subscription_type="trial" if subscription.is_trial else "paid", autopay_enabled=bool(subscription.autopay_enabled), branding=settings.get_miniapp_branding(), ) -def _safe_int(value: Any) -> int: - try: - return int(value) - except (TypeError, ValueError): - return 0 - - -def _normalize_period_discounts( - raw: Optional[Dict[Any, Any]] -) -> Dict[int, int]: - if not isinstance(raw, dict): - return {} - - normalized: Dict[int, int] = {} - for key, value in raw.items(): - try: - period = int(key) - normalized[period] = int(value) - except (TypeError, ValueError): - continue - - return normalized - - -def _extract_promo_discounts(group: Optional[PromoGroup]) -> Dict[str, Any]: - if not group: - return { - "server_discount_percent": 0, - "traffic_discount_percent": 0, - "device_discount_percent": 0, - "period_discounts": {}, - "apply_discounts_to_addons": True, - } - - return { - "server_discount_percent": max(0, _safe_int(getattr(group, "server_discount_percent", 0))), - "traffic_discount_percent": max(0, _safe_int(getattr(group, "traffic_discount_percent", 0))), - "device_discount_percent": max(0, _safe_int(getattr(group, "device_discount_percent", 0))), - "period_discounts": _normalize_period_discounts(getattr(group, "period_discounts", None)), - "apply_discounts_to_addons": bool( - getattr(group, "apply_discounts_to_addons", True) - ), - } - - diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index 360e6fa5..3461e170 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -39,26 +39,6 @@ class MiniAppSubscriptionUser(BaseModel): class MiniAppPromoGroup(BaseModel): id: int name: str - server_discount_percent: int = 0 - traffic_discount_percent: int = 0 - device_discount_percent: int = 0 - period_discounts: Dict[int, int] = Field(default_factory=dict) - apply_discounts_to_addons: bool = True - - -class MiniAppAutoPromoGroupLevel(BaseModel): - id: int - name: str - threshold_kopeks: int - threshold_rubles: float - threshold_label: str - is_reached: bool = False - is_current: bool = False - server_discount_percent: int = 0 - traffic_discount_percent: int = 0 - device_discount_percent: int = 0 - period_discounts: Dict[int, int] = Field(default_factory=dict) - apply_discounts_to_addons: bool = True class MiniAppConnectedServer(BaseModel): @@ -110,10 +90,6 @@ class MiniAppSubscriptionResponse(BaseModel): balance_currency: Optional[str] = None transactions: List[MiniAppTransaction] = Field(default_factory=list) promo_group: Optional[MiniAppPromoGroup] = None - auto_assign_promo_groups: List[MiniAppAutoPromoGroupLevel] = Field(default_factory=list) - total_spent_kopeks: int = 0 - total_spent_rubles: float = 0.0 - total_spent_label: Optional[str] = None subscription_type: str autopay_enabled: bool = False branding: Optional[MiniAppBranding] = None diff --git a/miniapp/index.html b/miniapp/index.html index dd12503f..29d0c127 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -560,10 +560,6 @@ border-radius: var(--radius-sm); } - .info-item.promo-group-info { - align-items: flex-start; - } - .info-label { font-size: 14px; color: var(--text-secondary); @@ -580,311 +576,6 @@ text-align: right; } - .promo-group-value { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 8px; - width: 100%; - } - - .promo-group-chip { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 12px; - border-radius: 999px; - background: rgba(var(--primary-rgb), 0.12); - color: var(--primary); - font-size: 13px; - font-weight: 600; - border: 1px solid rgba(var(--primary-rgb), 0.16); - min-width: 140px; - text-align: center; - } - - .promo-group-chip.muted { - color: var(--text-secondary); - background: rgba(var(--primary-rgb), 0.05); - border-color: rgba(var(--primary-rgb), 0.1); - } - - .promo-group-discounts, - .promo-discount-row { - display: flex; - flex-wrap: wrap; - gap: 6px; - justify-content: flex-end; - } - - .promo-group-period-discounts, - .promo-period-discounts { - display: flex; - flex-direction: column; - gap: 4px; - align-items: flex-end; - } - - .promo-periods-title { - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--text-secondary); - } - - .promo-group-period-discounts .promo-periods-title { - width: 100%; - text-align: right; - } - - .promo-period-badges { - display: flex; - flex-wrap: wrap; - gap: 6px; - justify-content: flex-end; - } - - .promo-period-badge { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: 999px; - background: rgba(var(--primary-rgb), 0.08); - border: 1px solid rgba(var(--primary-rgb), 0.12); - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - white-space: nowrap; - } - - .promo-period-badge .promo-period-value { - color: var(--primary); - } - - .promo-group-note { - font-size: 12px; - color: var(--warning); - background: rgba(245, 158, 11, 0.08); - padding: 6px 10px; - border-radius: var(--radius-sm); - border: 1px solid rgba(245, 158, 11, 0.25); - max-width: 260px; - text-align: right; - } - - .promo-periods-empty { - font-size: 12px; - color: var(--text-secondary); - text-align: right; - } - - /* Promo levels */ - .promo-levels-card.expandable .card-content { - padding-bottom: 0; - } - - .promo-levels-card.expanded .card-content { - padding-bottom: 16px; - } - - .promo-levels-card .card-header { - align-items: flex-start; - } - - .promo-levels-card .card-header .expand-icon { - margin-top: 4px; - } - - .promo-header-content { - display: flex; - flex-direction: column; - gap: 6px; - flex: 1; - } - - .promo-levels-header-meta { - display: flex; - flex-wrap: wrap; - gap: 6px; - } - - .promo-levels-header-chip { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: 999px; - background: rgba(var(--primary-rgb), 0.08); - border: 1px solid rgba(var(--primary-rgb), 0.12); - font-size: 12px; - font-weight: 600; - color: var(--text-primary); - } - - .promo-levels-header-chip.muted { - color: var(--text-secondary); - background: rgba(var(--primary-rgb), 0.05); - } - - .promo-level-summary { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 20px 12px; - font-weight: 600; - color: var(--text-primary); - } - - .promo-level-summary { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 20px 12px; - font-weight: 600; - color: var(--text-primary); - } - - .promo-level-summary-label { - color: var(--text-secondary); - font-size: 13px; - } - - .promo-level-summary-value { - font-size: 16px; - } - - .promo-level-list { - list-style: none; - padding: 0 20px 8px; - margin: 0; - display: flex; - flex-direction: column; - gap: 12px; - } - - .promo-level-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 14px 16px; - border-radius: var(--radius); - border: 1px solid var(--border-color); - background: var(--bg-secondary); - transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease; - } - - .promo-level-item:hover { - border-color: rgba(var(--primary-rgb), 0.4); - box-shadow: var(--shadow-sm); - transform: translateY(-1px); - } - - .promo-level-item.current { - border-color: var(--primary); - box-shadow: var(--shadow-sm); - } - - .promo-level-info { - display: flex; - flex-direction: column; - gap: 4px; - } - - .promo-level-discounts { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 8px; - } - - .promo-period-discounts .promo-periods-title { - align-self: flex-start; - } - - .promo-level-name { - font-weight: 700; - color: var(--text-primary); - font-size: 15px; - } - - .promo-level-threshold { - font-size: 13px; - color: var(--text-secondary); - } - - .promo-level-badge { - padding: 6px 12px; - border-radius: 999px; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.3px; - } - - .promo-level-item.current .promo-level-badge { - background: rgba(var(--primary-rgb), 0.18); - color: var(--primary); - } - - .promo-level-item.reached .promo-level-badge { - background: rgba(16, 185, 129, 0.18); - color: var(--success); - } - - .promo-level-item.locked .promo-level-badge { - background: rgba(148, 163, 184, 0.18); - color: var(--text-secondary); - } - - .promo-discount-badge { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 4px 8px; - border-radius: 999px; - border: 1px solid rgba(var(--primary-rgb), 0.12); - background: rgba(var(--primary-rgb), 0.08); - color: var(--text-secondary); - font-size: 12px; - font-weight: 600; - white-space: nowrap; - } - - .promo-discount-badge .promo-discount-value { - color: var(--text-primary); - } - - .promo-discount-badge.muted { - background: rgba(var(--primary-rgb), 0.05); - border-color: rgba(var(--primary-rgb), 0.08); - } - - .promo-discount-badge.muted .promo-discount-value { - color: var(--text-secondary); - } - - .promo-level-item.current .promo-discount-badge { - border-color: rgba(var(--primary-rgb), 0.35); - color: var(--primary); - } - - .promo-level-item.current .promo-discount-badge .promo-discount-value { - color: var(--primary); - } - - .promo-level-item.reached .promo-discount-badge { - border-color: rgba(16, 185, 129, 0.35); - color: var(--success); - } - - .promo-level-item.reached .promo-discount-badge .promo-discount-value { - color: var(--success); - } - - .promo-level-item.locked .promo-discount-badge { - opacity: 0.8; - } - /* Balance Card */ .balance-card { background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.1), rgba(var(--primary-rgb), 0.05)); @@ -1551,45 +1242,6 @@ color: var(--text-secondary); } - :root[data-theme="dark"] .promo-discount-badge { - background: rgba(var(--primary-rgb), 0.12); - border-color: rgba(var(--primary-rgb), 0.2); - } - - :root[data-theme="dark"] .promo-discount-badge.muted { - background: rgba(148, 163, 184, 0.15); - border-color: rgba(148, 163, 184, 0.25); - color: rgba(226, 232, 240, 0.75); - } - - :root[data-theme="dark"] .promo-discount-badge.muted .promo-discount-value { - color: rgba(226, 232, 240, 0.75); - } - - :root[data-theme="dark"] .promo-group-chip, - :root[data-theme="dark"] .promo-levels-header-chip, - :root[data-theme="dark"] .promo-period-badge { - background: rgba(var(--primary-rgb), 0.16); - border-color: rgba(var(--primary-rgb), 0.28); - color: var(--text-primary); - } - - :root[data-theme="dark"] .promo-group-chip.muted, - :root[data-theme="dark"] .promo-levels-header-chip.muted { - background: rgba(148, 163, 184, 0.18); - border-color: rgba(148, 163, 184, 0.28); - color: var(--text-secondary); - } - - :root[data-theme="dark"] .promo-period-badge .promo-period-value { - color: var(--primary); - } - - :root[data-theme="dark"] .promo-group-note { - background: rgba(245, 158, 11, 0.18); - border-color: rgba(245, 158, 11, 0.45); - } - :root[data-theme="dark"] body, :root[data-theme="dark"] .card, :root[data-theme="dark"] .user-card, @@ -1728,15 +1380,6 @@ Type - -
- Promo group -
- - - - - -
-
Device Limit - @@ -1748,38 +1391,6 @@
- - -