minor fix marketplace + xyi ego znaet
This commit is contained in:
@ -133,7 +133,7 @@ const AppLayout = () => {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: location.pathname === '/profile'
|
||||
justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch')
|
||||
? 'center'
|
||||
: 'flex-start',
|
||||
overflowX: 'hidden',
|
||||
|
||||
48
src/renderer/assets.d.ts
vendored
Normal file
48
src/renderer/assets.d.ts
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
type Styles = Record<string, string>;
|
||||
|
||||
declare module '*.svg' {
|
||||
import React = require('react');
|
||||
|
||||
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.scss' {
|
||||
const content: Styles;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.sass' {
|
||||
const content: Styles;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.css' {
|
||||
const content: Styles;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.mp3' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
declare module '*.wav' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
declare module '*.ogg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
BIN
src/renderer/assets/sounds/buy.mp3
Normal file
BIN
src/renderer/assets/sounds/buy.mp3
Normal file
Binary file not shown.
BIN
src/renderer/assets/sounds/sell.mp3
Normal file
BIN
src/renderer/assets/sounds/sell.mp3
Normal file
Binary file not shown.
@ -53,28 +53,28 @@ export default function CoinsDisplay({
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return {
|
||||
containerPadding: '4px 8px',
|
||||
iconSize: '16px',
|
||||
fontSize: '12px',
|
||||
borderRadius: '12px',
|
||||
gap: '6px',
|
||||
containerPadding: '0.4vw 0.8vw',
|
||||
iconSize: '1.4vw',
|
||||
fontSize: '1vw',
|
||||
borderRadius: '2vw',
|
||||
gap: '0.6vw',
|
||||
};
|
||||
case 'large':
|
||||
return {
|
||||
containerPadding: '8px 16px',
|
||||
iconSize: '28px',
|
||||
fontSize: '18px',
|
||||
borderRadius: '20px',
|
||||
gap: '10px',
|
||||
containerPadding: '0.4vw 1.2vw',
|
||||
iconSize: '2.2vw',
|
||||
fontSize: '1.6vw',
|
||||
borderRadius: '1.8vw',
|
||||
gap: '0.8vw',
|
||||
};
|
||||
case 'medium':
|
||||
default:
|
||||
return {
|
||||
containerPadding: '6px 12px',
|
||||
iconSize: '24px',
|
||||
fontSize: '16px',
|
||||
borderRadius: '16px',
|
||||
gap: '8px',
|
||||
containerPadding: '0.4vw 1vw',
|
||||
iconSize: '2vw',
|
||||
fontSize: '1.4vw',
|
||||
borderRadius: '1.6vw',
|
||||
gap: '0.6vw',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,8 +3,7 @@ import { Box, Button, TextField, Typography, InputAdornment, IconButton } from '
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import GradientTextField from '../GradientTextField';
|
||||
import GradientVisibilityToggleIcon from '../Icons/GradientVisibilityToggleIcon'
|
||||
import GradientVisibilityIcon from '../Icons/GradientVisibilityIcon'
|
||||
import GradientVisibilityToggleIcon from '../../assets/Icons/GradientVisibilityToggleIcon'
|
||||
|
||||
interface AuthFormProps {
|
||||
config: {
|
||||
|
||||
@ -1,25 +1,187 @@
|
||||
import { Slider } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { Box, Slider, Typography } from '@mui/material';
|
||||
|
||||
interface MemorySliderProps {
|
||||
memory: number;
|
||||
onChange: (e: Event, value: number | number[]) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
const MemorySlider = ({ memory, onChange }: MemorySliderProps) => {
|
||||
const gradientPrimary =
|
||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)';
|
||||
|
||||
const formatMb = (v: number) => `${v} MB`;
|
||||
const formatGb = (v: number) => `${(v / 1024).toFixed(v % 1024 === 0 ? 0 : 1)} GB`;
|
||||
|
||||
const MemorySlider = ({
|
||||
memory,
|
||||
onChange,
|
||||
min = 1024,
|
||||
max = 32768,
|
||||
step = 1024,
|
||||
}: MemorySliderProps) => {
|
||||
// marks только на “красивых” значениях, чтобы не было каши
|
||||
const marks = [
|
||||
{ value: 1024, label: '1 GB' },
|
||||
{ value: 4096, label: '4 GB' },
|
||||
{ value: 8192, label: '8 GB' },
|
||||
{ value: 16384, label: '16 GB' },
|
||||
{ value: 32768, label: '32 GB' },
|
||||
].filter((m) => m.value >= min && m.value <= max);
|
||||
|
||||
return (
|
||||
<Slider
|
||||
name="memory"
|
||||
aria-label="Memory"
|
||||
defaultValue={4096}
|
||||
valueLabelDisplay="auto"
|
||||
shiftStep={1024}
|
||||
step={1024}
|
||||
marks
|
||||
min={1024}
|
||||
max={32628}
|
||||
value={memory}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'baseline',
|
||||
justifyContent: 'space-between',
|
||||
mb: '1.2vh',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
|
||||
fontWeight: 800,
|
||||
fontSize: '1.1vw',
|
||||
color: '#fff',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
>
|
||||
Память
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
|
||||
fontWeight: 800,
|
||||
fontSize: '1.1vw',
|
||||
backgroundImage: gradientPrimary,
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
{memory >= 1024 ? formatGb(memory) : formatMb(memory)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Slider
|
||||
name="memory"
|
||||
aria-label="Memory"
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(v) => (v >= 1024 ? formatGb(v as number) : formatMb(v as number))}
|
||||
shiftStep={step}
|
||||
step={step}
|
||||
marks={marks}
|
||||
min={min}
|
||||
max={max}
|
||||
value={memory}
|
||||
onChange={onChange}
|
||||
sx={{
|
||||
px: '0.2vw',
|
||||
|
||||
// rail (фон полосы)
|
||||
'& .MuiSlider-rail': {
|
||||
opacity: 1,
|
||||
height: '0.9vh',
|
||||
borderRadius: '999vw',
|
||||
backgroundColor: 'rgba(255,255,255,0.10)',
|
||||
boxShadow: 'inset 0 0.25vh 0.6vh rgba(0,0,0,0.45)',
|
||||
},
|
||||
|
||||
// track (заполненная часть)
|
||||
'& .MuiSlider-track': {
|
||||
height: '0.9vh',
|
||||
borderRadius: '999vw',
|
||||
border: 'none',
|
||||
background: gradientPrimary,
|
||||
boxShadow: '0 0.6vh 1.6vh rgba(233,64,205,0.18)',
|
||||
},
|
||||
|
||||
// thumb (ползунок)
|
||||
'& .MuiSlider-thumb': {
|
||||
width: '1.6vw',
|
||||
height: '1.6vw',
|
||||
minWidth: 14,
|
||||
minHeight: 14,
|
||||
borderRadius: '999vw',
|
||||
background: 'rgba(10,10,20,0.92)',
|
||||
border: '0.22vw solid rgba(255,255,255,0.18)',
|
||||
boxShadow:
|
||||
'0 0.9vh 2.4vh rgba(0,0,0,0.55), 0 0 1.2vw rgba(242,113,33,0.20)',
|
||||
transition: 'transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease',
|
||||
'&:hover': {
|
||||
// transform: 'scale(1.06)',
|
||||
borderColor: 'rgba(242,113,33,0.55)',
|
||||
boxShadow:
|
||||
'0 1.1vh 2.8vh rgba(0,0,0,0.62), 0 0 1.6vw rgba(233,64,205,0.28)',
|
||||
},
|
||||
|
||||
// внутренний “свет”
|
||||
'&:before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: '18%',
|
||||
borderRadius: '999vw',
|
||||
background: gradientPrimary,
|
||||
opacity: 0.85,
|
||||
filter: 'blur(0.3vw)',
|
||||
},
|
||||
},
|
||||
|
||||
// value label (плашка значения)
|
||||
'& .MuiSlider-valueLabel': {
|
||||
fontFamily: 'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
|
||||
fontSize: '0.85vw',
|
||||
borderRadius: '1.2vw',
|
||||
padding: '0.4vh 0.8vw',
|
||||
color: '#fff',
|
||||
background: 'rgba(0,0,0,0.55)',
|
||||
border: '1px solid rgba(255,255,255,0.10)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
boxShadow: '0 1.2vh 3vh rgba(0,0,0,0.55)',
|
||||
'&:before': { display: 'none' },
|
||||
},
|
||||
|
||||
// marks (точки)
|
||||
'& .MuiSlider-mark': {
|
||||
width: '0.35vw',
|
||||
height: '0.35vw',
|
||||
minWidth: 4,
|
||||
minHeight: 4,
|
||||
borderRadius: '999vw',
|
||||
backgroundColor: 'rgba(255,255,255,0.18)',
|
||||
},
|
||||
'& .MuiSlider-markActive': {
|
||||
backgroundColor: 'rgba(255,255,255,0.55)',
|
||||
},
|
||||
|
||||
// mark labels (подписи)
|
||||
'& .MuiSlider-markLabel': {
|
||||
color: 'rgba(255,255,255,0.55)',
|
||||
fontSize: '0.75vw',
|
||||
marginTop: '1vh',
|
||||
userSelect: 'none',
|
||||
},
|
||||
|
||||
// focus outline
|
||||
'& .MuiSlider-thumb.Mui-focusVisible': {
|
||||
outline: 'none',
|
||||
boxShadow:
|
||||
'0 0 0 0.25vw rgba(242,113,33,0.20), 0 1.1vh 2.8vh rgba(0,0,0,0.62)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Subtext */}
|
||||
<Typography sx={{ mt: '1.2vh', color: 'rgba(255,255,255,0.55)', fontSize: '0.85vw' }}>
|
||||
Шаг: {formatGb(step)} • Рекомендуем: 4–8 GB для большинства сборок
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ interface SettingsModalProps {
|
||||
memory: number;
|
||||
preserveFiles: string[];
|
||||
};
|
||||
onConfigChange: (newConfig: {
|
||||
onConfigChange: (updater: (prev: { memory: number; preserveFiles: string[] }) => {
|
||||
memory: number;
|
||||
preserveFiles: string[];
|
||||
}) => void;
|
||||
@ -58,7 +58,7 @@ const SettingsModal = ({
|
||||
packName={packName}
|
||||
initialSelected={config.preserveFiles}
|
||||
onSelectionChange={(selected) => {
|
||||
onConfigChange({ ...config, preserveFiles: selected });
|
||||
onConfigChange((prev) => ({ ...prev, preserveFiles: selected }));
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body1" sx={{ color: 'white' }}>
|
||||
@ -66,8 +66,9 @@ const SettingsModal = ({
|
||||
</Typography>
|
||||
<MemorySlider
|
||||
memory={config.memory}
|
||||
onChange={(e, value) => {
|
||||
onConfigChange({ ...config, memory: value as number });
|
||||
onChange={(_, value) => {
|
||||
const next = Array.isArray(value) ? value[0] : value;
|
||||
onConfigChange((prev) => ({ ...prev, memory: next }));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@ -245,142 +245,159 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
// px: { xs: 2, md: 4 },
|
||||
// py: { xs: 2, md: 3 },
|
||||
mt: '-3vh',
|
||||
width: '85%',
|
||||
overflowY: 'auto',
|
||||
paddingTop: '5vh',
|
||||
paddingBottom: '5vh',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '85vw',
|
||||
height: '100%',
|
||||
paddingBottom: '5vh',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 4,
|
||||
borderRadius: '1.2vw',
|
||||
overflow: 'hidden',
|
||||
background:
|
||||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.18), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.14), transparent 55%), rgba(10,10,20,0.94)',
|
||||
boxShadow: '0 18px 60px rgba(0,0,0,0.55)',
|
||||
boxShadow: '0 1.2vw 3.8vw rgba(0,0,0,0.55)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxHeight: '76vh', // подстрой под свой layout
|
||||
}}
|
||||
>
|
||||
{/* alerts */}
|
||||
<Box sx={{ px: { xs: 2, md: 3 }, pt: 2 }}>
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 1.5 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{success && (
|
||||
<Alert severity="success" sx={{ mb: 1.5 }}>
|
||||
{success}
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
px: { xs: 2, md: 3 },
|
||||
pb: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 5,
|
||||
background:
|
||||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.18), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.14), transparent 55%), rgba(10,10,20,0.94)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
}}
|
||||
>
|
||||
<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',
|
||||
fontWeight: 800,
|
||||
fontSize: '1rem',
|
||||
color: '#fff',
|
||||
lineHeight: 1.15,
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
Серия дней: <b>{streak}</b>
|
||||
</Typography>
|
||||
{/* alerts */}
|
||||
<Box sx={{ px: '2vw', pt: '1.2vh' }}>
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 1.5 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{success && (
|
||||
<Alert severity="success" sx={{ mb: 1.5 }}>
|
||||
{success}
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<CustomTooltip title="К текущему месяцу">
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
px: '2vw',
|
||||
pb: '2vw',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: '2vw',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
minWidth: 220,
|
||||
display: 'flex',
|
||||
gap: '1vw',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ color: 'rgba(255,255,255,0.75)', display: 'flex', gap: '0.7vw' }}
|
||||
>
|
||||
<CoinsDisplay value={todaysReward} size="small" />
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily:
|
||||
'Benzin-Bold, system-ui, -apple-system, Segoe UI, Roboto, Arial',
|
||||
fontWeight: 800,
|
||||
fontSize: '2vw',
|
||||
color: '#fff',
|
||||
lineHeight: 1.15,
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
Серия дней: <b>{streak}</b>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<CustomTooltip title="К текущему месяцу">
|
||||
<IconButton
|
||||
onClick={goToday}
|
||||
sx={{
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
bgcolor: 'rgba(0,0,0,0.22)',
|
||||
'&:hover': { bgcolor: 'rgba(255,255,255,0.08)' },
|
||||
}}
|
||||
>
|
||||
<TodayRoundedIcon sx={{ fontSize: '2vw', p: '0.2vw' }} />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
|
||||
<IconButton
|
||||
onClick={goToday}
|
||||
onClick={goPrev}
|
||||
sx={{
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
bgcolor: 'rgba(0,0,0,0.22)',
|
||||
'&:hover': { bgcolor: 'rgba(255,255,255,0.08)' },
|
||||
}}
|
||||
>
|
||||
<TodayRoundedIcon sx={{ fontSize: '2vw', p: '0.2vw' }} />
|
||||
<ChevronLeftRoundedIcon sx={{ fontSize: '2vw', p: '0.2vw' }} />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
|
||||
<IconButton
|
||||
onClick={goPrev}
|
||||
sx={{
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
bgcolor: 'rgba(0,0,0,0.22)',
|
||||
'&:hover': { bgcolor: 'rgba(255,255,255,0.08)' },
|
||||
}}
|
||||
>
|
||||
<ChevronLeftRoundedIcon />
|
||||
</IconButton>
|
||||
<Box sx={{ minWidth: 160, textAlign: 'center', maxWidth: '15vw' }}>
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#fff',
|
||||
fontWeight: 800,
|
||||
letterSpacing: 0.2,
|
||||
fontSize: '1.5vw',
|
||||
}}
|
||||
>
|
||||
{RU_MONTHS[viewMonth]} {viewYear}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ minWidth: 160, textAlign: 'center', maxWidth: '10vw' }}>
|
||||
<Typography
|
||||
<IconButton
|
||||
onClick={goNext}
|
||||
sx={{
|
||||
color: '#fff',
|
||||
fontWeight: 800,
|
||||
letterSpacing: 0.2,
|
||||
fontSize: { xs: 14.5, md: 16 },
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
bgcolor: 'rgba(0,0,0,0.22)',
|
||||
'&:hover': { bgcolor: 'rgba(255,255,255,0.08)' },
|
||||
}}
|
||||
>
|
||||
{RU_MONTHS[viewMonth]} {viewYear}
|
||||
</Typography>
|
||||
</Box>
|
||||
<ChevronRightRoundedIcon sx={{ fontSize: '2vw', p: '0.2vw' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<IconButton
|
||||
onClick={goNext}
|
||||
sx={{
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
bgcolor: 'rgba(0,0,0,0.22)',
|
||||
'&:hover': { bgcolor: 'rgba(255,255,255,0.08)' },
|
||||
}}
|
||||
>
|
||||
<ChevronRightRoundedIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.10)' }} />
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.10)' }} />
|
||||
|
||||
{/* Calendar */}
|
||||
<Box sx={{ px: { xs: 2, md: 3 }, py: 2.5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
px: '2vw',
|
||||
py: '2vh',
|
||||
overflowY: 'auto',
|
||||
flex: 1, // занимает всё оставшееся место под шапкой
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
gap: 1,
|
||||
mb: 1.2,
|
||||
gap: '0.7vw',
|
||||
mb: '1.2vh',
|
||||
}}
|
||||
>
|
||||
{RU_WEEKDAYS.map((w, i) => (
|
||||
@ -388,7 +405,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
key={w}
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
fontSize: 'clamp(10px, 1.1vw, 14px)',
|
||||
fontWeight: 700,
|
||||
color:
|
||||
i >= 5 ? 'rgba(255,255,255,0.75)' : 'rgba(255,255,255,0.6)',
|
||||
@ -404,7 +421,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
gap: 1,
|
||||
gap: '0.7vw',
|
||||
}}
|
||||
>
|
||||
{grid.map(({ date, inCurrentMonth }) => {
|
||||
@ -422,7 +439,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
sx={{
|
||||
width: '100%',
|
||||
aspectRatio: '1 / 1',
|
||||
borderRadius: 3,
|
||||
borderRadius: '1vw',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
border: isSelected
|
||||
@ -471,7 +488,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
fontSize: '1.3vw',
|
||||
fontWeight: 800,
|
||||
color: inCurrentMonth
|
||||
? '#fff'
|
||||
@ -484,7 +501,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 10.5,
|
||||
fontSize: '1vw',
|
||||
color: claimed
|
||||
? 'rgba(156, 255, 198, 0.9)'
|
||||
: isToday
|
||||
@ -501,12 +518,12 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 8,
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 999,
|
||||
bottom: '1vh',
|
||||
width: '0.45vw',
|
||||
height: '0.45vw',
|
||||
borderRadius: '999vw',
|
||||
bgcolor: 'rgba(156, 255, 198, 0.95)',
|
||||
boxShadow: '0 0 12px rgba(156, 255, 198, 0.35)',
|
||||
boxShadow: '0 0 1vw rgba(156, 255, 198, 0.35)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -519,17 +536,17 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
{/* Footer actions */}
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2.2,
|
||||
mt: '2vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
gap: '1vw',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{ color: 'rgba(255,255,255,0.65)', fontSize: 12 }}
|
||||
sx={{ color: 'rgba(255,255,255,0.65)', fontSize: '1.2vw' }}
|
||||
>
|
||||
Выбрано:{' '}
|
||||
<span style={{ color: '#fff', fontWeight: 800 }}>
|
||||
@ -552,8 +569,8 @@ export default function DailyReward({ onClaimed }: Props) {
|
||||
disabled={loading || !status?.ok || !canClaim}
|
||||
onClick={handleClaim}
|
||||
sx={{
|
||||
px: 3,
|
||||
py: 1.2,
|
||||
px: '2.4vw',
|
||||
py: '1vh',
|
||||
borderRadius: '2vw',
|
||||
textTransform: 'uppercase',
|
||||
fontFamily:
|
||||
|
||||
@ -2,8 +2,6 @@ import {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
Snackbar,
|
||||
Alert,
|
||||
LinearProgress,
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -13,6 +11,8 @@ import PopaPopa from '../components/popa-popa';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import React from 'react';
|
||||
import SettingsModal from '../components/Settings/SettingsModal';
|
||||
import CustomNotification from '../components/Notifications/CustomNotification';
|
||||
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -62,11 +62,16 @@ const LaunchPage = ({
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [buffer, setBuffer] = useState(10);
|
||||
const [installStatus, setInstallStatus] = useState('');
|
||||
const [notification, setNotification] = useState<{
|
||||
open: boolean;
|
||||
message: string;
|
||||
severity: 'success' | 'error' | 'info';
|
||||
}>({ open: false, message: '', severity: 'info' });
|
||||
const [notifOpen, setNotifOpen] = useState(false);
|
||||
const [notifMsg, setNotifMsg] = useState<React.ReactNode>('');
|
||||
const [notifSeverity, setNotifSeverity] = useState<
|
||||
'success' | 'info' | 'warning' | 'error'
|
||||
>('info');
|
||||
|
||||
const [notifPos, setNotifPos] = useState<NotificationPosition>({
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
});
|
||||
const [installStep, setInstallStep] = useState('');
|
||||
const [installMessage, setInstallMessage] = useState('');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
@ -223,14 +228,14 @@ const LaunchPage = ({
|
||||
}, [versionId]);
|
||||
|
||||
const showNotification = (
|
||||
message: string,
|
||||
severity: 'success' | 'error' | 'info',
|
||||
message: React.ReactNode,
|
||||
severity: 'success' | 'info' | 'warning' | 'error' = 'info',
|
||||
position: NotificationPosition = { vertical: 'bottom', horizontal: 'center' },
|
||||
) => {
|
||||
setNotification({ open: true, message, severity });
|
||||
};
|
||||
|
||||
const handleCloseNotification = () => {
|
||||
setNotification({ ...notification, open: false });
|
||||
setNotifMsg(message);
|
||||
setNotifSeverity(severity);
|
||||
setNotifPos(position);
|
||||
setNotifOpen(true);
|
||||
};
|
||||
|
||||
// Функция для запуска игры с настройками выбранной версии
|
||||
@ -522,19 +527,14 @@ const LaunchPage = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Snackbar
|
||||
open={notification.open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleCloseNotification}
|
||||
>
|
||||
<Alert
|
||||
onClose={handleCloseNotification}
|
||||
severity={notification.severity}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<CustomNotification
|
||||
open={notifOpen}
|
||||
message={notifMsg}
|
||||
severity={notifSeverity}
|
||||
position={notifPos}
|
||||
onClose={() => setNotifOpen(false)}
|
||||
autoHideDuration={2500}
|
||||
/>
|
||||
|
||||
<SettingsModal
|
||||
open={open}
|
||||
|
||||
@ -11,13 +11,15 @@ import {
|
||||
Pagination,
|
||||
Tabs,
|
||||
Tab,
|
||||
Alert,
|
||||
Snackbar,
|
||||
} from '@mui/material';
|
||||
import { isPlayerOnline, getPlayerServer } from '../utils/playerOnlineCheck';
|
||||
import { buyItem, fetchMarketplace, MarketplaceResponse, Server } from '../api';
|
||||
import PlayerInventory from '../components/PlayerInventory';
|
||||
import { FullScreenLoader } from '../components/FullScreenLoader';
|
||||
import CustomNotification from '../components/Notifications/CustomNotification';
|
||||
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
||||
import * as React from 'react';
|
||||
import { playBuySound } from '../utils/sounds';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
@ -53,14 +55,15 @@ export default function Marketplace() {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [totalPages, setTotalPages] = useState<number>(1);
|
||||
const [tabValue, setTabValue] = useState<number>(0);
|
||||
const [notification, setNotification] = useState<{
|
||||
open: boolean;
|
||||
message: string;
|
||||
type: 'success' | 'error';
|
||||
}>({
|
||||
open: false,
|
||||
message: '',
|
||||
type: 'success',
|
||||
const [notifOpen, setNotifOpen] = useState(false);
|
||||
const [notifMsg, setNotifMsg] = useState<React.ReactNode>('');
|
||||
const [notifSeverity, setNotifSeverity] = useState<
|
||||
'success' | 'info' | 'warning' | 'error'
|
||||
>('info');
|
||||
|
||||
const [notifPos, setNotifPos] = useState<NotificationPosition>({
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
});
|
||||
|
||||
const translateServer = (server: Server) => {
|
||||
@ -137,13 +140,14 @@ export default function Marketplace() {
|
||||
if (username) {
|
||||
const result = await buyItem(username, itemId);
|
||||
|
||||
setNotification({
|
||||
open: true,
|
||||
message:
|
||||
result.message ||
|
||||
playBuySound();
|
||||
|
||||
showNotification(
|
||||
result.message ||
|
||||
'Предмет успешно куплен! Он будет добавлен в ваш инвентарь.',
|
||||
type: 'success',
|
||||
});
|
||||
'success',
|
||||
{ vertical: 'bottom', horizontal: 'left' },
|
||||
);
|
||||
|
||||
// Обновляем список предметов
|
||||
if (playerServer) {
|
||||
@ -152,20 +156,23 @@ export default function Marketplace() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при покупке предмета:', error);
|
||||
setNotification({
|
||||
open: true,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Ошибка при покупке предмета',
|
||||
type: 'error',
|
||||
});
|
||||
showNotification(
|
||||
error instanceof Error ? error.message : 'Ошибка при покупке предмета',
|
||||
'error',
|
||||
{ vertical: 'bottom', horizontal: 'left' },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Закрытие уведомления
|
||||
const handleCloseNotification = () => {
|
||||
setNotification({ ...notification, open: false });
|
||||
const showNotification = (
|
||||
message: React.ReactNode,
|
||||
severity: 'success' | 'info' | 'warning' | 'error' = 'info',
|
||||
position: NotificationPosition = { vertical: 'bottom', horizontal: 'center' },
|
||||
) => {
|
||||
setNotifMsg(message);
|
||||
setNotifSeverity(severity);
|
||||
setNotifPos(position);
|
||||
setNotifOpen(true);
|
||||
};
|
||||
|
||||
// Получаем имя пользователя из localStorage при монтировании компонента
|
||||
@ -199,6 +206,14 @@ export default function Marketplace() {
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<CustomNotification
|
||||
open={notifOpen}
|
||||
message={notifMsg}
|
||||
severity={notifSeverity}
|
||||
position={notifPos}
|
||||
onClose={() => setNotifOpen(false)}
|
||||
autoHideDuration={2500}
|
||||
/>
|
||||
<FullScreenLoader
|
||||
fullScreen={true}
|
||||
message="Проверяем, находитесь ли вы на сервере..."
|
||||
@ -466,10 +481,9 @@ export default function Marketplace() {
|
||||
}
|
||||
|
||||
// Показываем уведомление
|
||||
setNotification({
|
||||
open: true,
|
||||
message: 'Предмет успешно выставлен на продажу!',
|
||||
type: 'success',
|
||||
showNotification('Предмет успешно выставлен на продажу!', 'success', {
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -483,21 +497,14 @@ export default function Marketplace() {
|
||||
</Typography>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Уведомления */}
|
||||
<Snackbar
|
||||
open={notification.open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleCloseNotification}
|
||||
>
|
||||
<Alert
|
||||
onClose={handleCloseNotification}
|
||||
severity={notification.type}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<CustomNotification
|
||||
open={notifOpen}
|
||||
message={notifMsg}
|
||||
severity={notifSeverity}
|
||||
position={notifPos}
|
||||
onClose={() => setNotifOpen(false)}
|
||||
autoHideDuration={2500}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import { getPlayerServer } from '../utils/playerOnlineCheck';
|
||||
import CaseRoulette from '../components/CaseRoulette';
|
||||
import BonusShopItem from '../components/BonusShopItem';
|
||||
import ShopItem from '../components/ShopItem';
|
||||
import { playBuySound } from '../utils/sounds';
|
||||
|
||||
function getRarityByWeight(
|
||||
weight?: number,
|
||||
@ -157,6 +158,9 @@ export default function Shop() {
|
||||
try {
|
||||
await purchaseCape(username, cape_id);
|
||||
await loadUserCapes(username);
|
||||
|
||||
playBuySound();
|
||||
|
||||
setNotification({
|
||||
open: true,
|
||||
message: 'Плащ успешно куплен!',
|
||||
@ -260,6 +264,9 @@ export default function Shop() {
|
||||
await withProcessing(bonusTypeId, async () => {
|
||||
try {
|
||||
const res = await purchaseBonus(username, bonusTypeId);
|
||||
|
||||
playBuySound();
|
||||
|
||||
setNotification({
|
||||
open: true,
|
||||
message: res.message || 'Прокачка успешно куплена!',
|
||||
|
||||
13
src/renderer/utils/sounds.ts
Normal file
13
src/renderer/utils/sounds.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import buySound from '../assets/sounds/buy.mp3';
|
||||
|
||||
let buyAudio: HTMLAudioElement | null = null;
|
||||
|
||||
export const playBuySound = () => {
|
||||
if (!buyAudio) {
|
||||
buyAudio = new Audio(buySound);
|
||||
buyAudio.volume = 0.6;
|
||||
}
|
||||
|
||||
buyAudio.currentTime = 0;
|
||||
buyAudio.play().catch(() => {});
|
||||
};
|
||||
Reference in New Issue
Block a user