From e8409a9007e25c685ceaa4b6a88c54237b02303a Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 24 Jul 2026 06:42:39 +0300 Subject: [PATCH] =?UTF-8?q?feat(ws):=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE?= =?UTF-8?q?=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=D1=82=D0=B8=D1=8F=D1=85=20=D0=A1=D0=91=D0=9F-?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D0=BF=D1=80=D0=BE=D0=B4=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/WebSocketNotifications.tsx | 127 +++++++++++++++++++++- src/providers/WebSocketContext.ts | 3 + 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/components/WebSocketNotifications.tsx b/src/components/WebSocketNotifications.tsx index f8b38ea..31e0ab1 100644 --- a/src/components/WebSocketNotifications.tsx +++ b/src/components/WebSocketNotifications.tsx @@ -7,7 +7,7 @@ import { useCallback } from 'react'; import { useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from '@tanstack/react-query'; -import { useWebSocket, WSMessage } from '../hooks/useWebSocket'; +import { useWebSocket, type WSMessage } from '../hooks/useWebSocket'; import { useToast } from './Toast'; import { useAuthStore } from '../store/auth'; import { useCurrency } from '../hooks/useCurrency'; @@ -296,6 +296,131 @@ export default function WebSocketNotifications() { return; } + // SBP recurring events + if (type === 'sbp_recurring.confirmed') { + const amount = message.amount_rubles ?? (message.amount_kopeks ?? 0) / 100; + showToast({ + type: 'success', + title: t('wsNotifications.sbpRecurring.confirmedTitle', 'SBP payment confirmed'), + message: t( + 'wsNotifications.sbpRecurring.confirmedMessage', + 'Payment of {{amount}} {{currency}} was charged successfully', + { + amount: formatAmount(amount), + currency: currencySymbol, + }, + ), + icon: 🔁, + onClick: () => navigate('/subscriptions'), + duration: 8000, + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + refreshUser(); + return; + } + + if (type === 'sbp_recurring.activated') { + showToast({ + type: 'success', + title: t('wsNotifications.sbpRecurring.activatedTitle', 'SBP auto-payment connected'), + message: t( + 'wsNotifications.sbpRecurring.activatedMessage', + 'SBP auto-payment has been successfully connected', + ), + icon: 🔁, + onClick: () => navigate('/subscriptions'), + duration: 8000, + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring', + }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); + refreshUser(); + return; + } + + if (type === 'sbp_recurring.failed') { + showToast({ + type: 'error', + title: t('wsNotifications.sbpRecurring.failedTitle', 'SBP payment failed'), + message: t( + 'wsNotifications.sbpRecurring.failedMessage', + 'Failed to charge your SBP auto-payment', + ), + icon: , + onClick: () => navigate('/subscriptions'), + duration: 10000, + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring', + }); + return; + } + + if (type === 'sbp_recurring.past_due') { + showToast({ + type: 'warning', + title: t('wsNotifications.sbpRecurring.pastDueTitle', 'SBP payment overdue'), + message: t( + 'wsNotifications.sbpRecurring.pastDueMessage', + 'Your SBP auto-payment is overdue. Please check your banking app.', + ), + icon: ⚠️, + onClick: () => navigate('/subscriptions'), + duration: 10000, + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring', + }); + return; + } + + if (type === 'sbp_recurring.cancelled') { + showToast({ + type: 'warning', + title: t('wsNotifications.sbpRecurring.cancelledTitle', 'SBP auto-payment disabled'), + message: t( + 'wsNotifications.sbpRecurring.cancelledMessage', + 'SBP auto-payment has been disabled', + ), + icon: 🔕, + onClick: () => navigate('/subscriptions'), + duration: 8000, + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription', + }); + queryClient.invalidateQueries({ + predicate: (query) => + Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring', + }); + return; + } + // Account events if (type === 'account.banned') { showToast({ diff --git a/src/providers/WebSocketContext.ts b/src/providers/WebSocketContext.ts index 6c4cbb1..8a23823 100644 --- a/src/providers/WebSocketContext.ts +++ b/src/providers/WebSocketContext.ts @@ -21,6 +21,9 @@ export interface WSMessage { new_expires_at?: string; tariff_name?: string; days_left?: number; + // SBP recurring events + status?: string; + next_charge_at?: string | null; // Device purchase events devices_added?: number; new_device_limit?: number;