feat: subscription selector in VPN connection block for multi-tariff

- Show VPN connection block even when panelInfo not yet loaded
- Add subscription dropdown when user has multiple subscriptions
- Show spinner while loading, "no data" when panel returns not found
- Changing subscription in VPN block reloads panel info for that sub
This commit is contained in:
Fringg
2026-04-29 06:34:41 +03:00
parent 853e1c9c84
commit bc37f31350
3 changed files with 101 additions and 74 deletions

View File

@@ -3081,6 +3081,7 @@
"lastConnection": "Last connection", "lastConnection": "Last connection",
"firstConnection": "First connection", "firstConnection": "First connection",
"online": "Online", "online": "Online",
"noVpnData": "No connection data available",
"requestHistory": "Subscription request history", "requestHistory": "Subscription request history",
"requestHistoryTotal": "Total requests", "requestHistoryTotal": "Total requests",
"requestAt": "Date", "requestAt": "Date",

View File

@@ -3488,6 +3488,7 @@
"lastConnection": "Последнее подключение", "lastConnection": "Последнее подключение",
"firstConnection": "Первое подключение", "firstConnection": "Первое подключение",
"online": "Онлайн", "online": "Онлайн",
"noVpnData": "Нет данных о подключении",
"requestHistory": "История запросов подписки", "requestHistory": "История запросов подписки",
"requestHistoryTotal": "Всего запросов", "requestHistoryTotal": "Всего запросов",
"requestAt": "Дата", "requestAt": "Дата",

View File

@@ -1376,11 +1376,35 @@ export default function AdminUserDetail() {
</div> </div>
{/* VPN Connection Info */} {/* VPN Connection Info */}
{panelInfo && panelInfo.found && ( {(panelInfo || userSubscriptions.length > 0) && (
<div className="rounded-xl border border-dark-700/30 bg-dark-800/50 p-4"> <div className="rounded-xl border border-dark-700/30 bg-dark-800/50 p-4">
<div className="mb-3 text-sm font-medium text-dark-200"> <div className="mb-3 flex items-center justify-between">
<span className="text-sm font-medium text-dark-200">
{t('admin.users.detail.vpnConnection')} {t('admin.users.detail.vpnConnection')}
</span>
{userSubscriptions.length > 1 && (
<select
value={activeSubscriptionId ?? ''}
onChange={(e) => {
const subId = Number(e.target.value);
setActiveSubscriptionId(subId);
}}
className="rounded-lg border border-dark-600 bg-dark-700 px-3 py-1.5 text-xs text-dark-200"
>
{userSubscriptions.map((s) => (
<option key={s.id} value={s.id}>
{s.tariff_name || `#${s.id}`} {s.status}
</option>
))}
</select>
)}
</div> </div>
{panelInfoLoading && !panelInfo?.found && (
<div className="flex items-center justify-center py-4">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
</div>
)}
{panelInfo?.found && (
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="rounded-lg bg-dark-700/30 p-3"> <div className="rounded-lg bg-dark-700/30 p-3">
<div className="mb-1 text-xs text-dark-500"> <div className="mb-1 text-xs text-dark-500">
@@ -1448,11 +1472,12 @@ export default function AdminUserDetail() {
</div> </div>
)} )}
</div> </div>
)}
{!panelInfoLoading && !panelInfo?.found && userSubscriptions.length > 0 && (
<div className="py-2 text-center text-xs text-dark-500">
{t('admin.users.detail.noVpnData')}
</div> </div>
)} )}
{panelInfoLoading && !panelInfo && (
<div className="flex justify-center rounded-xl bg-dark-800/50 py-4">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
</div> </div>
)} )}