mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat: auto-close modals when success notification appears
- Add closeOthersSignal to success notification store - Create useCloseOnSuccessNotification hook for modals to subscribe - Update TopUpModal to auto-close on success events - Update InsufficientBalancePrompt to auto-close on success events - Update Subscription page to close all modals/forms on success events This ensures that when a WebSocket event indicates successful payment or subscription purchase, all related modals close automatically, providing clear feedback to users.
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { balanceApi } from '../api/balance';
|
import { balanceApi } from '../api/balance';
|
||||||
import { useCurrency } from '../hooks/useCurrency';
|
import { useCurrency } from '../hooks/useCurrency';
|
||||||
|
import { useCloseOnSuccessNotification } from '../store/successNotification';
|
||||||
import TopUpModal from './TopUpModal';
|
import TopUpModal from './TopUpModal';
|
||||||
import type { PaymentMethod } from '../types';
|
import type { PaymentMethod } from '../types';
|
||||||
|
|
||||||
@@ -28,6 +29,13 @@ export default function InsufficientBalancePrompt({
|
|||||||
const [showMethodSelect, setShowMethodSelect] = useState(false);
|
const [showMethodSelect, setShowMethodSelect] = useState(false);
|
||||||
const [selectedMethod, setSelectedMethod] = useState<PaymentMethod | null>(null);
|
const [selectedMethod, setSelectedMethod] = useState<PaymentMethod | null>(null);
|
||||||
|
|
||||||
|
// Auto-close modals when success notification appears
|
||||||
|
const handleCloseAll = useCallback(() => {
|
||||||
|
setShowMethodSelect(false);
|
||||||
|
setSelectedMethod(null);
|
||||||
|
}, []);
|
||||||
|
useCloseOnSuccessNotification(handleCloseAll);
|
||||||
|
|
||||||
const { data: paymentMethods } = useQuery({
|
const { data: paymentMethods } = useQuery({
|
||||||
queryKey: ['payment-methods'],
|
queryKey: ['payment-methods'],
|
||||||
queryFn: balanceApi.getPaymentMethods,
|
queryFn: balanceApi.getPaymentMethods,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { balanceApi } from '../api/balance';
|
|||||||
import { useCurrency } from '../hooks/useCurrency';
|
import { useCurrency } from '../hooks/useCurrency';
|
||||||
import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
|
import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
|
||||||
import { checkRateLimit, getRateLimitResetTime, RATE_LIMIT_KEYS } from '../utils/rateLimit';
|
import { checkRateLimit, getRateLimitResetTime, RATE_LIMIT_KEYS } from '../utils/rateLimit';
|
||||||
|
import { useCloseOnSuccessNotification } from '../store/successNotification';
|
||||||
import type { PaymentMethod } from '../types';
|
import type { PaymentMethod } from '../types';
|
||||||
import BentoCard from './ui/BentoCard';
|
import BentoCard from './ui/BentoCard';
|
||||||
|
|
||||||
@@ -144,6 +145,9 @@ export default function TopUpModal({ method, onClose, initialAmountRubles }: Top
|
|||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
|
// Auto-close when success notification appears (e.g., balance topped up via WebSocket)
|
||||||
|
useCloseOnSuccessNotification(handleClose);
|
||||||
|
|
||||||
// Keyboard: Escape to close (PC)
|
// Keyboard: Escape to close (PC)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@@ -15,6 +15,7 @@ import type {
|
|||||||
import ConnectionModal from '../components/ConnectionModal';
|
import ConnectionModal from '../components/ConnectionModal';
|
||||||
import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt';
|
import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt';
|
||||||
import { useCurrency } from '../hooks/useCurrency';
|
import { useCurrency } from '../hooks/useCurrency';
|
||||||
|
import { useCloseOnSuccessNotification } from '../store/successNotification';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
|
|
||||||
// Helper to extract error message from axios/api errors
|
// Helper to extract error message from axios/api errors
|
||||||
@@ -279,6 +280,21 @@ export default function Subscription() {
|
|||||||
|
|
||||||
// Tariff switch preview
|
// Tariff switch preview
|
||||||
const [switchTariffId, setSwitchTariffId] = useState<number | null>(null);
|
const [switchTariffId, setSwitchTariffId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Auto-close all modals/forms when success notification appears (e.g., subscription purchased via WebSocket)
|
||||||
|
const handleCloseAllModals = useCallback(() => {
|
||||||
|
setShowConnectionModal(false);
|
||||||
|
setShowPurchaseForm(false);
|
||||||
|
setShowTariffPurchase(false);
|
||||||
|
setShowDeviceTopup(false);
|
||||||
|
setShowTrafficTopup(false);
|
||||||
|
setShowServerManagement(false);
|
||||||
|
setSwitchTariffId(null);
|
||||||
|
setSelectedTariff(null);
|
||||||
|
setSelectedTariffPeriod(null);
|
||||||
|
}, []);
|
||||||
|
useCloseOnSuccessNotification(handleCloseAllModals);
|
||||||
|
|
||||||
const { data: switchPreview, isLoading: switchPreviewLoading } = useQuery({
|
const { data: switchPreview, isLoading: switchPreviewLoading } = useQuery({
|
||||||
queryKey: ['tariff-switch-preview', switchTariffId],
|
queryKey: ['tariff-switch-preview', switchTariffId],
|
||||||
queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!),
|
queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
export type SuccessNotificationType =
|
export type SuccessNotificationType =
|
||||||
@@ -25,6 +26,8 @@ export interface SuccessNotificationData {
|
|||||||
interface SuccessNotificationState {
|
interface SuccessNotificationState {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
data: SuccessNotificationData | null;
|
data: SuccessNotificationData | null;
|
||||||
|
/** Signal that increments when other modals should close */
|
||||||
|
closeOthersSignal: number;
|
||||||
|
|
||||||
show: (data: SuccessNotificationData) => void;
|
show: (data: SuccessNotificationData) => void;
|
||||||
hide: () => void;
|
hide: () => void;
|
||||||
@@ -33,7 +36,30 @@ interface SuccessNotificationState {
|
|||||||
export const useSuccessNotification = create<SuccessNotificationState>((set) => ({
|
export const useSuccessNotification = create<SuccessNotificationState>((set) => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
data: null,
|
data: null,
|
||||||
|
closeOthersSignal: 0,
|
||||||
|
|
||||||
show: (data) => set({ isOpen: true, data }),
|
show: (data) =>
|
||||||
|
set((state) => ({
|
||||||
|
isOpen: true,
|
||||||
|
data,
|
||||||
|
// Increment signal to tell other modals to close
|
||||||
|
closeOthersSignal: state.closeOthersSignal + 1,
|
||||||
|
})),
|
||||||
hide: () => set({ isOpen: false, data: null }),
|
hide: () => set({ isOpen: false, data: null }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that calls onClose when a success notification appears.
|
||||||
|
* Use this in modals that should auto-close on success events.
|
||||||
|
*/
|
||||||
|
export function useCloseOnSuccessNotification(onClose: () => void) {
|
||||||
|
const closeOthersSignal = useSuccessNotification((state) => state.closeOthersSignal);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Skip the initial render (signal = 0)
|
||||||
|
if (closeOthersSignal > 0) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [closeOthersSignal]);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user