Files
bedolaga-cabinet/src/components/InsufficientBalancePrompt.tsx
c0mrade 562ab7abf7 refactor: full codebase cleanup, dependency updates, and lint fixes
Phase 0: Remove ~920 lines of dead code (ThemeBentoPicker, PromoDiscountBadge,
AdminLayout, SettingsSidebar, MovingGradient, EmptyState, miniapp API, unused
types/functions/transitions/skeleton helpers). Fix API barrel file (add 20 missing exports).

Phase 1: Add ErrorBoundary (app/page/widget levels), centralize constants,
add axios timeout, fix staleTime:0 in React Query.

Phase 2: Consolidate hexToHsl, extract email validation, fix duplicate code.

Phase 3: Fix auth race condition (await checkAdminStatus), memoize useCurrency,
add WebSocket message validation.

Phase 4: Extract useBranding, useFeatureFlags, useScrollRestoration from AppShell.

Phase 5: Remove all eslint-disable react-hooks/exhaustive-deps (14 total),
simplify logger, remove deprecated hooks (useBackButton, useTelegramDnd,
useTelegramWebApp).

Dependencies: React 19, react-router 7, zustand 5, i18next 25, react-i18next 16,
eslint-plugin-react-refresh 0.5. Remove unused @lottiefiles/dotlottie-react.
Convert vite manualChunks to function-based approach for react-router v7 compat.

Lint: Fix logger.ts no-unused-expressions, fix react-refresh/only-export-components
in 6 Radix primitive files (const re-exports → direct re-exports), fix
WebSocketProvider exhaustive-deps. Result: 0 errors, 0 warnings.

Add CLAUDE.md to .gitignore.
2026-02-06 16:55:55 +03:00

151 lines
5.2 KiB
TypeScript

import { useState } from 'react';
import { useNavigate, useLocation } from 'react-router';
import { useTranslation } from 'react-i18next';
import { useCurrency } from '../hooks/useCurrency';
interface InsufficientBalancePromptProps {
/** Amount missing in kopeks */
missingAmountKopeks: number;
/** Optional custom message */
message?: string;
/** Compact mode for inline use */
compact?: boolean;
/** Additional className */
className?: string;
/** Callback to execute before opening top-up modal (e.g., save cart) */
onBeforeTopUp?: () => Promise<void>;
}
export default function InsufficientBalancePrompt({
missingAmountKopeks,
message,
compact = false,
className = '',
onBeforeTopUp,
}: InsufficientBalancePromptProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const { formatAmount, currencySymbol } = useCurrency();
const [isPreparingTopUp, setIsPreparingTopUp] = useState(false);
const missingRubles = missingAmountKopeks / 100;
const displayAmount = formatAmount(missingRubles);
const handleTopUpClick = async () => {
if (onBeforeTopUp) {
setIsPreparingTopUp(true);
try {
await onBeforeTopUp();
} catch {
// Silently ignore errors - still navigate
} finally {
setIsPreparingTopUp(false);
}
}
const params = new URLSearchParams();
params.set('amount', String(Math.ceil(missingRubles)));
params.set('returnTo', location.pathname);
navigate(`/balance/top-up?${params.toString()}`);
};
if (compact) {
return (
<div
className={`flex items-center justify-between gap-3 rounded-xl border border-error-500/30 bg-error-500/10 p-3 ${className}`}
>
<div className="flex items-center gap-2 text-sm text-error-400">
<svg
className="h-4 w-4 flex-shrink-0"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
/>
</svg>
<span>
{message || t('balance.insufficientFunds')}:{' '}
<span className="font-semibold">
{displayAmount} {currencySymbol}
</span>
</span>
</div>
<button
onClick={handleTopUpClick}
disabled={isPreparingTopUp}
className="btn-primary whitespace-nowrap px-3 py-1.5 text-xs"
>
{isPreparingTopUp ? (
<span className="h-3 w-3 animate-spin rounded-full border border-white/30 border-t-white" />
) : (
t('balance.topUp')
)}
</button>
</div>
);
}
return (
<div
className={`rounded-xl border border-error-500/30 bg-gradient-to-br from-error-500/10 to-warning-500/5 p-4 ${className}`}
>
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-error-500/20">
<svg
className="h-5 w-5 text-error-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z"
/>
</svg>
</div>
<div className="min-w-0 flex-1">
<div className="mb-1 font-medium text-error-400">{t('balance.insufficientFunds')}</div>
<div className="text-sm text-dark-300">{message || t('balance.topUpToComplete')}</div>
<div className="mt-3 flex items-center gap-3">
<div className="text-lg font-bold text-dark-100">
{t('balance.missing')}:{' '}
<span className="text-error-400">
{displayAmount} {currencySymbol}
</span>
</div>
</div>
</div>
</div>
<button
onClick={handleTopUpClick}
disabled={isPreparingTopUp}
className="btn-primary mt-4 flex w-full items-center justify-center gap-2 py-2.5"
>
{isPreparingTopUp ? (
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
) : (
<>
<svg
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
{t('balance.topUpBalance')}
</>
)}
</button>
</div>
);
}