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 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,
|
||||
}}
|
||||
>
|
||||
@ -415,10 +497,24 @@ 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>
|
||||
|
||||
@ -427,7 +523,8 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-block',
|
||||
cursor: loading || !status?.ok || !canClaim ? 'help' : 'pointer',
|
||||
cursor:
|
||||
loading || !status?.ok || !canClaim ? 'help' : 'pointer',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user