diff --git a/src/components/SuccessNotificationModal.tsx b/src/components/SuccessNotificationModal.tsx
new file mode 100644
index 0000000..8bd9ced
--- /dev/null
+++ b/src/components/SuccessNotificationModal.tsx
@@ -0,0 +1,284 @@
+/**
+ * Global success notification modal.
+ * Shows prominent success messages for balance top-ups and subscription purchases.
+ */
+
+import { useEffect, useCallback } from 'react';
+import { createPortal } from 'react-dom';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+import { useSuccessNotification } from '../store/successNotification';
+import { useCurrency } from '../hooks/useCurrency';
+import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
+
+// Icons
+const CheckCircleIcon = () => (
+
+);
+
+const WalletIcon = () => (
+
+);
+
+const RocketIcon = () => (
+
+);
+
+const CloseIcon = () => (
+
+);
+
+export default function SuccessNotificationModal() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const { isOpen, data, hide } = useSuccessNotification();
+ const { formatAmount, currencySymbol } = useCurrency();
+ const { webApp, safeAreaInset, contentSafeAreaInset, isTelegramWebApp } = useTelegramWebApp();
+
+ const safeBottom = isTelegramWebApp
+ ? Math.max(safeAreaInset.bottom, contentSafeAreaInset.bottom)
+ : 0;
+
+ const handleClose = useCallback(() => {
+ hide();
+ }, [hide]);
+
+ // Escape key to close
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ handleClose();
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyDown);
+ return () => document.removeEventListener('keydown', handleKeyDown);
+ }, [isOpen, handleClose]);
+
+ // Telegram back button
+ useEffect(() => {
+ if (!isOpen || !webApp?.BackButton) return;
+
+ webApp.BackButton.show();
+ webApp.BackButton.onClick(handleClose);
+
+ return () => {
+ webApp.BackButton.offClick(handleClose);
+ webApp.BackButton.hide();
+ };
+ }, [isOpen, webApp, handleClose]);
+
+ // Scroll lock
+ useEffect(() => {
+ if (!isOpen) return;
+
+ document.body.style.overflow = 'hidden';
+ return () => {
+ document.body.style.overflow = '';
+ };
+ }, [isOpen]);
+
+ if (!isOpen || !data) return null;
+
+ const isBalanceTopup = data.type === 'balance_topup';
+ const isSubscription =
+ data.type === 'subscription_activated' ||
+ data.type === 'subscription_renewed' ||
+ data.type === 'subscription_purchased';
+
+ // Format amount
+ const formattedAmount = data.amountKopeks
+ ? `${formatAmount(data.amountKopeks / 100)} ${currencySymbol}`
+ : null;
+
+ // Format new balance
+ const formattedBalance =
+ data.newBalanceKopeks !== undefined
+ ? `${formatAmount(data.newBalanceKopeks / 100)} ${currencySymbol}`
+ : null;
+
+ // Format expiry date
+ const formattedExpiry = data.expiresAt
+ ? new Date(data.expiresAt).toLocaleDateString(undefined, {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ })
+ : null;
+
+ // Determine title and message
+ let title = data.title;
+ const message = data.message;
+ let icon =
{message}
} +