Files
popa-launcher/src/renderer/components/CoinsDisplay.tsx
2025-12-20 19:32:47 +05:00

274 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// CoinsDisplay.tsx
import { Box, Typography } from '@mui/material';
import CustomTooltip from './Notifications/CustomTooltip';
import { useEffect, useMemo, useState } from 'react';
import { fetchCoins } from '../api';
import type { SxProps, Theme } from '@mui/material/styles';
interface CoinsDisplayProps {
value?: number;
username?: string;
size?: 'small' | 'medium' | 'large';
showTooltip?: boolean;
tooltipText?: string;
showIcon?: boolean;
iconColor?: string;
autoUpdate?: boolean;
updateInterval?: number;
backgroundColor?: string;
textColor?: string;
onClick?: () => void;
disableRefreshOnClick?: boolean;
sx?: SxProps<Theme>;
}
export default function CoinsDisplay({
value: externalValue,
username,
size = 'medium',
showTooltip = true,
tooltipText = 'Попы — внутриигровая валюта, начисляемая за время игры на серверах.',
showIcon = true,
iconColor = '#2bff00ff',
autoUpdate = false,
updateInterval = 60000,
backgroundColor = 'rgba(0, 0, 0, 0.2)',
textColor = 'white',
onClick,
disableRefreshOnClick = false,
sx,
}: CoinsDisplayProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [settingsVersion, setSettingsVersion] = useState(0);
const storageKey = useMemo(() => {
// ключ под конкретного пользователя
return username ? `coins:${username}` : 'coins:anonymous';
}, [username]);
const readCachedCoins = (): number | null => {
if (typeof window === 'undefined') return null;
try {
const raw = localStorage.getItem(storageKey);
if (!raw) return null;
const parsed = Number(raw);
return Number.isFinite(parsed) ? parsed : null;
} catch {
return null;
}
};
const handleClick = () => {
// 1) если передали внешний обработчик — выполняем его
if (onClick) onClick();
// 2) опционально оставляем обновление баланса по клику
if (!disableRefreshOnClick && username) fetchCoinsData();
};
const [coins, setCoins] = useState<number>(() => {
// 1) если пришло значение извне — оно приоритетнее
if (externalValue !== undefined) return externalValue;
// 2) иначе пробуем localStorage
const cached = readCachedCoins();
if (cached !== null) return cached;
// 3) иначе 0
return 0;
});
useEffect(() => {
const handler = () => setSettingsVersion((v) => v + 1);
window.addEventListener('settings-updated', handler as EventListener);
return () =>
window.removeEventListener('settings-updated', handler as EventListener);
}, []);
const isTooltipDisabledBySettings = useMemo(() => {
try {
const raw = localStorage.getItem('launcher_settings');
if (!raw) return false;
const s = JSON.parse(raw);
return Boolean(s?.disableToolTip);
} catch {
return false;
}
}, [settingsVersion]);
const tooltipEnabled = showTooltip && !isTooltipDisabledBySettings;
const getSizes = () => {
switch (size) {
case 'small':
return {
containerPadding: '0.4vw 0.8vw',
iconSize: '1.4vw',
fontSize: '1vw',
borderRadius: '2vw',
gap: '0.6vw',
};
case 'large':
return {
containerPadding: '0.4vw 1.2vw',
iconSize: '2.2vw',
fontSize: '1.6vw',
borderRadius: '1.8vw',
gap: '0.8vw',
};
case 'medium':
default:
return {
containerPadding: '0.4vw 1vw',
iconSize: '2vw',
fontSize: '1.4vw',
borderRadius: '1.6vw',
gap: '0.6vw',
};
}
};
const sizes = getSizes();
const formatNumber = (num: number): string => {
return num.toLocaleString('ru-RU');
};
// Сохраняем актуальный баланс в localStorage при любом изменении coins
useEffect(() => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(storageKey, String(coins));
} catch {
// игнорируем (private mode, quota и т.п.)
}
}, [coins, storageKey]);
// Если пришло внешнее значение — обновляем и оно же попадёт в localStorage через эффект выше
useEffect(() => {
if (externalValue !== undefined) {
setCoins(externalValue);
}
}, [externalValue]);
// При смене username можно сразу подхватить кэш, чтобы не мигало при первом fetch
useEffect(() => {
if (externalValue !== undefined) return; // внешнее значение важнее
const cached = readCachedCoins();
if (cached !== null) setCoins(cached);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [storageKey]);
const fetchCoinsData = async () => {
if (!username) return;
setIsLoading(true);
try {
const coinsData = await fetchCoins(username);
// ВАЖНО: не показываем "..." — просто меняем число, когда пришёл ответ
setCoins(coinsData.coins);
} catch (error) {
console.error('Ошибка при получении количества монет:', error);
// оставляем старое значение (из state/localStorage)
} finally {
setIsLoading(false);
}
};
useEffect(() => {
if (username && autoUpdate) {
fetchCoinsData();
const coinsInterval = setInterval(fetchCoinsData, updateInterval);
return () => clearInterval(coinsInterval);
}
}, [username, autoUpdate, updateInterval]);
const handleRefresh = () => {
if (username) fetchCoinsData();
};
const coinsDisplay = (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: sizes.gap,
backgroundColor,
borderRadius: sizes.borderRadius,
padding: sizes.containerPadding,
border: '1px solid rgba(255, 255, 255, 0.1)',
cursor: onClick ? 'pointer' : tooltipEnabled ? 'help' : 'default',
// можно оставить лёгкий намёк на загрузку, но без "пульса" текста
opacity: isLoading ? 0.85 : 1,
transition: 'opacity 0.2s ease',
...sx,
}}
onClick={handleClick}
title={username ? 'Нажмите для обновления' : undefined}
>
{showIcon && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: sizes.iconSize,
height: sizes.iconSize,
borderRadius: '50%',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
}}
>
<Typography
sx={{
color: iconColor,
fontWeight: 'bold',
fontSize: `calc(${sizes.fontSize} * 0.8)`,
}}
>
P
</Typography>
</Box>
)}
<Typography
variant="body1"
sx={{
color: textColor,
fontWeight: 'bold',
fontSize: sizes.fontSize,
lineHeight: 1,
fontFamily: 'Benzin-Bold, sans-serif',
}}
>
{formatNumber(coins)}
</Typography>
</Box>
);
if (tooltipEnabled) {
return (
<CustomTooltip
title={tooltipText}
arrow
placement="bottom"
TransitionProps={{ timeout: 300 }}
>
{coinsDisplay}
</CustomTooltip>
);
}
return coinsDisplay;
}