fix overflow in daily rewards

This commit is contained in:
2025-12-13 16:27:30 +05:00
parent 712ae70e2a
commit 7d7136bac9

View File

@ -16,19 +16,38 @@ import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
import TodayRoundedIcon from '@mui/icons-material/TodayRounded';
import CustomTooltip from '../components/Notifications/CustomTooltip';
import CoinsDisplay from '../components/CoinsDisplay';
import { claimDaily, fetchDailyStatus, DailyStatusResponse, fetchDailyClaimDays } from '../api';
import {
claimDaily,
fetchDailyStatus,
DailyStatusResponse,
fetchDailyClaimDays,
} from '../api';
const RU_MONTHS = [
'Январь','Февраль','Март','Апрель','Май','Июнь',
'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь',
'Январь',
'Февраль',
'Март',
'Апрель',
'Май',
'Июнь',
'Июль',
'Август',
'Сентябрь',
'Октябрь',
'Ноябрь',
'Декабрь',
];
const RU_WEEKDAYS = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
const pad2 = (n: number) => String(n).padStart(2, '0');
const keyOf = (d: Date) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
const keyOf = (d: Date) =>
`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
const startOfDay = (d: Date) =>
new Date(d.getFullYear(), d.getMonth(), d.getDate());
const isSameDay = (a: Date, b: Date) =>
a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
const weekdayMonFirst = (date: Date) => (date.getDay() + 6) % 7;
const EKATERINBURG_TZ = 'Asia/Yekaterinburg';
@ -82,7 +101,9 @@ type DailyStatusCompat = DailyStatusResponse & {
export default function DailyReward({ onClaimed }: Props) {
const today = useMemo(() => startOfDay(new Date()), []);
const [view, setView] = useState(() => new Date(today.getFullYear(), today.getMonth(), 1));
const [view, setView] = useState(
() => new Date(today.getFullYear(), today.getMonth(), 1),
);
const [selected, setSelected] = useState<Date>(today);
// перенесённая логика статуса/клейма
@ -96,14 +117,19 @@ export default function DailyReward({ onClaimed }: Props) {
const viewYear = view.getFullYear();
const viewMonth = view.getMonth();
const grid = useMemo(() => buildCalendarGrid(viewYear, viewMonth), [viewYear, viewMonth]);
const grid = useMemo(
() => buildCalendarGrid(viewYear, viewMonth),
[viewYear, viewMonth],
);
const streak = status?.streak ?? 0;
const wasOnlineToday = status?.was_online_today ?? false;
const canClaim = (status?.can_claim ?? false) && wasOnlineToday;
const goPrev = () => setView((v) => new Date(v.getFullYear(), v.getMonth() - 1, 1));
const goNext = () => setView((v) => new Date(v.getFullYear(), v.getMonth() + 1, 1));
const goPrev = () =>
setView((v) => new Date(v.getFullYear(), v.getMonth() - 1, 1));
const goNext = () =>
setView((v) => new Date(v.getFullYear(), v.getMonth() + 1, 1));
const goToday = () => {
const t = new Date(today.getFullYear(), today.getMonth(), 1);
setView(t);
@ -156,13 +182,16 @@ export default function DailyReward({ onClaimed }: Props) {
}, [clientSecondsLeft]);
const todaysReward = useMemo(() => {
const effectiveStreak = canClaim ? Math.max(1, streak === 0 ? 1 : streak) : streak;
const effectiveStreak = canClaim
? Math.max(1, streak === 0 ? 1 : streak)
: streak;
return calcRewardByStreak(effectiveStreak);
}, [streak, canClaim]);
const subtitle = useMemo(() => {
if (!status) return '';
if (!wasOnlineToday) return 'Награда откроется после входа на сервер сегодня.';
if (!wasOnlineToday)
return 'Награда откроется после входа на сервер сегодня.';
if (canClaim) return 'Можно забрать прямо сейчас 🎁';
return `До следующей награды: ${formatHHMMSS(clientSecondsLeft)}`;
}, [status, wasOnlineToday, canClaim, clientSecondsLeft]);
@ -180,7 +209,9 @@ export default function DailyReward({ onClaimed }: Props) {
onClaimed?.(added);
} else {
if (res.reason === 'not_online_today') {
setError('Чтобы забрать награду — зайдите на сервер сегодня хотя бы на минуту.');
setError(
'Чтобы забрать награду — зайдите на сервер сегодня хотя бы на минуту.',
);
} else {
setError(res.reason || 'Награда недоступна');
}
@ -197,7 +228,17 @@ export default function DailyReward({ onClaimed }: Props) {
};
return (
<Box sx={{ px: { xs: 2, md: 4 }, py: { xs: 2, md: 3 }, mt: '-3vh', width: '85%' }}>
<Box
sx={{
// px: { xs: 2, md: 4 },
// py: { xs: 2, md: 3 },
mt: '-3vh',
width: '85%',
overflowY: 'auto',
paddingTop: '5vh',
paddingBottom: '5vh',
}}
>
<Paper
elevation={0}
sx={{
@ -233,14 +274,24 @@ export default function DailyReward({ onClaimed }: Props) {
gap: 2,
}}
>
<Box sx={{ minWidth: 220, display: 'flex', gap: '1vw', alignItems: 'center' }}>
<Typography sx={{ color: 'rgba(255,255,255,0.75)', display: 'flex', gap: 1 }}>
<Box
sx={{
minWidth: 220,
display: 'flex',
gap: '1vw',
alignItems: 'center',
}}
>
<Typography
sx={{ color: 'rgba(255,255,255,0.75)', display: 'flex', gap: 1 }}
>
<CoinsDisplay value={todaysReward} size="small" />
</Typography>
<Typography
sx={{
fontFamily: 'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
fontFamily:
'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
fontWeight: 800,
fontSize: '1rem',
color: '#fff',
@ -278,7 +329,14 @@ export default function DailyReward({ onClaimed }: Props) {
</IconButton>
<Box sx={{ minWidth: 160, textAlign: 'center', maxWidth: '10vw' }}>
<Typography sx={{ color: '#fff', fontWeight: 800, letterSpacing: 0.2, fontSize: { xs: 14.5, md: 16 } }}>
<Typography
sx={{
color: '#fff',
fontWeight: 800,
letterSpacing: 0.2,
fontSize: { xs: 14.5, md: 16 },
}}
>
{RU_MONTHS[viewMonth]} {viewYear}
</Typography>
</Box>
@ -300,7 +358,14 @@ export default function DailyReward({ onClaimed }: Props) {
{/* Calendar */}
<Box sx={{ px: { xs: 2, md: 3 }, py: 2.5 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1, mb: 1.2 }}>
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(7, 1fr)',
gap: 1,
mb: 1.2,
}}
>
{RU_WEEKDAYS.map((w, i) => (
<Typography
key={w}
@ -308,7 +373,8 @@ export default function DailyReward({ onClaimed }: Props) {
textAlign: 'center',
fontSize: 12,
fontWeight: 700,
color: i >= 5 ? 'rgba(255,255,255,0.75)' : 'rgba(255,255,255,0.6)',
color:
i >= 5 ? 'rgba(255,255,255,0.75)' : 'rgba(255,255,255,0.6)',
userSelect: 'none',
}}
>
@ -317,7 +383,13 @@ export default function DailyReward({ onClaimed }: Props) {
))}
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 1 }}>
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(7, 1fr)',
gap: 1,
}}
>
{grid.map(({ date, inCurrentMonth }) => {
const d = startOfDay(date);
const isToday = isSameDay(d, today);
@ -336,12 +408,19 @@ export default function DailyReward({ onClaimed }: Props) {
borderRadius: 3,
position: 'relative',
overflow: 'hidden',
border: isSelected ? '1px solid rgba(242,113,33,0.85)' : 'none',
bgcolor: inCurrentMonth ? 'rgba(0,0,0,0.24)' : 'rgba(0,0,0,0.12)',
transition: 'transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease',
border: isSelected
? '1px solid rgba(242,113,33,0.85)'
: 'none',
bgcolor: inCurrentMonth
? 'rgba(0,0,0,0.24)'
: 'rgba(0,0,0,0.12)',
transition:
'transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease',
transform: isSelected ? 'scale(1.02)' : 'scale(1)',
'&:hover': {
bgcolor: inCurrentMonth ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.04)',
bgcolor: inCurrentMonth
? 'rgba(255,255,255,0.06)'
: 'rgba(255,255,255,0.04)',
transform: 'translateY(-1px)',
},
}}
@ -351,7 +430,8 @@ export default function DailyReward({ onClaimed }: Props) {
sx={{
position: 'absolute',
inset: -20,
background: 'radial-gradient(circle at 50% 50%, rgba(233,64,205,0.22), transparent 55%)',
background:
'radial-gradient(circle at 50% 50%, rgba(233,64,205,0.22), transparent 55%)',
pointerEvents: 'none',
}}
/>
@ -373,7 +453,9 @@ export default function DailyReward({ onClaimed }: Props) {
sx={{
fontSize: 14,
fontWeight: 800,
color: inCurrentMonth ? '#fff' : 'rgba(255,255,255,0.35)',
color: inCurrentMonth
? '#fff'
: 'rgba(255,255,255,0.35)',
lineHeight: 1,
}}
>
@ -386,8 +468,8 @@ export default function DailyReward({ onClaimed }: Props) {
color: claimed
? 'rgba(156, 255, 198, 0.9)'
: isToday
? 'rgba(242,113,33,0.95)'
: 'rgba(255,255,255,0.45)',
? 'rgba(242,113,33,0.95)'
: 'rgba(255,255,255,0.45)',
fontWeight: 700,
userSelect: 'none',
}}
@ -415,52 +497,67 @@ export default function DailyReward({ onClaimed }: Props) {
</Box>
{/* Footer actions */}
<Box sx={{ mt: 2.2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2, flexWrap: 'wrap' }}>
<Box
sx={{
mt: 2.2,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 2,
flexWrap: 'wrap',
}}
>
<Box>
<Typography sx={{ color: 'rgba(255,255,255,0.65)', fontSize: 12 }}>
Выбрано: <span style={{ color: '#fff', fontWeight: 800 }}>{selectedKey}</span>
<Typography
sx={{ color: 'rgba(255,255,255,0.65)', fontSize: 12 }}
>
Выбрано:{' '}
<span style={{ color: '#fff', fontWeight: 800 }}>
{selectedKey}
</span>
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: '1.2vw', alignItems: 'center' }}>
<CustomTooltip title={subtitle} disableInteractive>
<Box
sx={{
display: 'inline-block',
cursor: loading || !status?.ok || !canClaim ? 'help' : 'pointer',
}}
>
<Button
variant="contained"
disabled={loading || !status?.ok || !canClaim}
onClick={handleClaim}
<CustomTooltip title={subtitle} disableInteractive>
<Box
sx={{
px: 3,
py: 1.2,
borderRadius: '2vw',
textTransform: 'uppercase',
fontFamily:
'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transition:
'transform 0.25s ease, box-shadow 0.25s ease, filter 0.25s ease',
'&:hover': {
transform: 'scale(0.98)',
filter: 'brightness(0.92)',
boxShadow: '0 0.5vw 1vw rgba(0, 0, 0, 0.3)',
},
'&.Mui-disabled': {
background: 'rgba(255,255,255,0.10)',
color: 'rgba(255,255,255,0.45)',
pointerEvents: 'none', // важно оставить
},
display: 'inline-block',
cursor:
loading || !status?.ok || !canClaim ? 'help' : 'pointer',
}}
>
{loading ? 'Забираем...' : 'Забрать'}
</Button>
</Box>
</CustomTooltip>
<Button
variant="contained"
disabled={loading || !status?.ok || !canClaim}
onClick={handleClaim}
sx={{
px: 3,
py: 1.2,
borderRadius: '2vw',
textTransform: 'uppercase',
fontFamily:
'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transition:
'transform 0.25s ease, box-shadow 0.25s ease, filter 0.25s ease',
'&:hover': {
transform: 'scale(0.98)',
filter: 'brightness(0.92)',
boxShadow: '0 0.5vw 1vw rgba(0, 0, 0, 0.3)',
},
'&.Mui-disabled': {
background: 'rgba(255,255,255,0.10)',
color: 'rgba(255,255,255,0.45)',
pointerEvents: 'none', // важно оставить
},
}}
>
{loading ? 'Забираем...' : 'Забрать'}
</Button>
</Box>
</CustomTooltip>
<CustomTooltip title="Сбросить выбор на сегодня">
<IconButton