fix overflow in daily rewards
This commit is contained in:
@ -16,19 +16,38 @@ import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
|
|||||||
import TodayRoundedIcon from '@mui/icons-material/TodayRounded';
|
import TodayRoundedIcon from '@mui/icons-material/TodayRounded';
|
||||||
import CustomTooltip from '../components/Notifications/CustomTooltip';
|
import CustomTooltip from '../components/Notifications/CustomTooltip';
|
||||||
import CoinsDisplay from '../components/CoinsDisplay';
|
import CoinsDisplay from '../components/CoinsDisplay';
|
||||||
import { claimDaily, fetchDailyStatus, DailyStatusResponse, fetchDailyClaimDays } from '../api';
|
import {
|
||||||
|
claimDaily,
|
||||||
|
fetchDailyStatus,
|
||||||
|
DailyStatusResponse,
|
||||||
|
fetchDailyClaimDays,
|
||||||
|
} from '../api';
|
||||||
|
|
||||||
const RU_MONTHS = [
|
const RU_MONTHS = [
|
||||||
'Январь','Февраль','Март','Апрель','Май','Июнь',
|
'Январь',
|
||||||
'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь',
|
'Февраль',
|
||||||
|
'Март',
|
||||||
|
'Апрель',
|
||||||
|
'Май',
|
||||||
|
'Июнь',
|
||||||
|
'Июль',
|
||||||
|
'Август',
|
||||||
|
'Сентябрь',
|
||||||
|
'Октябрь',
|
||||||
|
'Ноябрь',
|
||||||
|
'Декабрь',
|
||||||
];
|
];
|
||||||
const RU_WEEKDAYS = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
const RU_WEEKDAYS = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||||
|
|
||||||
const pad2 = (n: number) => String(n).padStart(2, '0');
|
const pad2 = (n: number) => String(n).padStart(2, '0');
|
||||||
const keyOf = (d: Date) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
const keyOf = (d: Date) =>
|
||||||
const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
`${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) =>
|
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 weekdayMonFirst = (date: Date) => (date.getDay() + 6) % 7;
|
||||||
|
|
||||||
const EKATERINBURG_TZ = 'Asia/Yekaterinburg';
|
const EKATERINBURG_TZ = 'Asia/Yekaterinburg';
|
||||||
@ -82,7 +101,9 @@ type DailyStatusCompat = DailyStatusResponse & {
|
|||||||
|
|
||||||
export default function DailyReward({ onClaimed }: Props) {
|
export default function DailyReward({ onClaimed }: Props) {
|
||||||
const today = useMemo(() => startOfDay(new Date()), []);
|
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);
|
const [selected, setSelected] = useState<Date>(today);
|
||||||
|
|
||||||
// перенесённая логика статуса/клейма
|
// перенесённая логика статуса/клейма
|
||||||
@ -96,14 +117,19 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
|
|
||||||
const viewYear = view.getFullYear();
|
const viewYear = view.getFullYear();
|
||||||
const viewMonth = view.getMonth();
|
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 streak = status?.streak ?? 0;
|
||||||
const wasOnlineToday = status?.was_online_today ?? false;
|
const wasOnlineToday = status?.was_online_today ?? false;
|
||||||
const canClaim = (status?.can_claim ?? false) && wasOnlineToday;
|
const canClaim = (status?.can_claim ?? false) && wasOnlineToday;
|
||||||
|
|
||||||
const goPrev = () => setView((v) => new Date(v.getFullYear(), v.getMonth() - 1, 1));
|
const goPrev = () =>
|
||||||
const goNext = () => setView((v) => new Date(v.getFullYear(), v.getMonth() + 1, 1));
|
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 goToday = () => {
|
||||||
const t = new Date(today.getFullYear(), today.getMonth(), 1);
|
const t = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||||
setView(t);
|
setView(t);
|
||||||
@ -156,13 +182,16 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
}, [clientSecondsLeft]);
|
}, [clientSecondsLeft]);
|
||||||
|
|
||||||
const todaysReward = useMemo(() => {
|
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);
|
return calcRewardByStreak(effectiveStreak);
|
||||||
}, [streak, canClaim]);
|
}, [streak, canClaim]);
|
||||||
|
|
||||||
const subtitle = useMemo(() => {
|
const subtitle = useMemo(() => {
|
||||||
if (!status) return '';
|
if (!status) return '';
|
||||||
if (!wasOnlineToday) return 'Награда откроется после входа на сервер сегодня.';
|
if (!wasOnlineToday)
|
||||||
|
return 'Награда откроется после входа на сервер сегодня.';
|
||||||
if (canClaim) return 'Можно забрать прямо сейчас 🎁';
|
if (canClaim) return 'Можно забрать прямо сейчас 🎁';
|
||||||
return `До следующей награды: ${formatHHMMSS(clientSecondsLeft)}`;
|
return `До следующей награды: ${formatHHMMSS(clientSecondsLeft)}`;
|
||||||
}, [status, wasOnlineToday, canClaim, clientSecondsLeft]);
|
}, [status, wasOnlineToday, canClaim, clientSecondsLeft]);
|
||||||
@ -180,7 +209,9 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
onClaimed?.(added);
|
onClaimed?.(added);
|
||||||
} else {
|
} else {
|
||||||
if (res.reason === 'not_online_today') {
|
if (res.reason === 'not_online_today') {
|
||||||
setError('Чтобы забрать награду — зайдите на сервер сегодня хотя бы на минуту.');
|
setError(
|
||||||
|
'Чтобы забрать награду — зайдите на сервер сегодня хотя бы на минуту.',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setError(res.reason || 'Награда недоступна');
|
setError(res.reason || 'Награда недоступна');
|
||||||
}
|
}
|
||||||
@ -197,7 +228,17 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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
|
<Paper
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
@ -233,14 +274,24 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
gap: 2,
|
gap: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ minWidth: 220, display: 'flex', gap: '1vw', alignItems: 'center' }}>
|
<Box
|
||||||
<Typography sx={{ color: 'rgba(255,255,255,0.75)', display: 'flex', gap: 1 }}>
|
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" />
|
<CoinsDisplay value={todaysReward} size="small" />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
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,
|
fontWeight: 800,
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
@ -278,7 +329,14 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Box sx={{ minWidth: 160, textAlign: 'center', maxWidth: '10vw' }}>
|
<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}
|
{RU_MONTHS[viewMonth]} {viewYear}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@ -300,7 +358,14 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
|
|
||||||
{/* Calendar */}
|
{/* Calendar */}
|
||||||
<Box sx={{ px: { xs: 2, md: 3 }, py: 2.5 }}>
|
<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) => (
|
{RU_WEEKDAYS.map((w, i) => (
|
||||||
<Typography
|
<Typography
|
||||||
key={w}
|
key={w}
|
||||||
@ -308,7 +373,8 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 700,
|
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',
|
userSelect: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -317,7 +383,13 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</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 }) => {
|
{grid.map(({ date, inCurrentMonth }) => {
|
||||||
const d = startOfDay(date);
|
const d = startOfDay(date);
|
||||||
const isToday = isSameDay(d, today);
|
const isToday = isSameDay(d, today);
|
||||||
@ -336,12 +408,19 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: isSelected ? '1px solid rgba(242,113,33,0.85)' : 'none',
|
border: isSelected
|
||||||
bgcolor: inCurrentMonth ? 'rgba(0,0,0,0.24)' : 'rgba(0,0,0,0.12)',
|
? '1px solid rgba(242,113,33,0.85)'
|
||||||
transition: 'transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease',
|
: '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)',
|
transform: isSelected ? 'scale(1.02)' : 'scale(1)',
|
||||||
'&:hover': {
|
'&: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)',
|
transform: 'translateY(-1px)',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -351,7 +430,8 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: -20,
|
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',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -373,7 +453,9 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
sx={{
|
sx={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
color: inCurrentMonth ? '#fff' : 'rgba(255,255,255,0.35)',
|
color: inCurrentMonth
|
||||||
|
? '#fff'
|
||||||
|
: 'rgba(255,255,255,0.35)',
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -415,10 +497,24 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Footer actions */}
|
{/* 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>
|
<Box>
|
||||||
<Typography sx={{ color: 'rgba(255,255,255,0.65)', fontSize: 12 }}>
|
<Typography
|
||||||
Выбрано: <span style={{ color: '#fff', fontWeight: 800 }}>{selectedKey}</span>
|
sx={{ color: 'rgba(255,255,255,0.65)', fontSize: 12 }}
|
||||||
|
>
|
||||||
|
Выбрано:{' '}
|
||||||
|
<span style={{ color: '#fff', fontWeight: 800 }}>
|
||||||
|
{selectedKey}
|
||||||
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -427,7 +523,8 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
cursor: loading || !status?.ok || !canClaim ? 'help' : 'pointer',
|
cursor:
|
||||||
|
loading || !status?.ok || !canClaim ? 'help' : 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user