Merge pull request #13 from Danya-Djan/frontend

Frontend
This commit is contained in:
Michail Kostocka 2024-12-17 14:20:44 +03:00 committed by GitHub
commit aa5805e14d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 1451 additions and 308 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
frontend/src/assets/dev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -2,8 +2,6 @@ import React, { useState } from 'react';
import styles from './auctioncard.module.css';
import { ETextStyles } from '../../texts';
import { PointsBlock } from '../../Elements/PointsBlock';
import { PersonIcon } from '../../Elements/PersonIcon';
import { UsersIcons } from '../../Elements/UsersIcons';
import { Button } from '../../Button';
import { EIcons } from '../../Icons';
import { Timer } from '../Timer';
@ -12,19 +10,23 @@ import { Slider } from '../../Elements/Slider';
import { ModalWindow } from '../../ModalWindow';
import { AuctionPopup } from '../AuctionPopup';
import { ResultAuctionPopup } from '../ResultAuctionPopup';
import { DevPopup } from '../../Elements/DevPopup';
import { useAppSelector } from '../../hooks/useAppSelector';
interface IAuctionCard {
auctionId: string,
name: string,
imgs: Array<string>,
minBet: string,
users: number,
prevBet: string,
myBetInit: string,
time: number,
isLead: boolean
isLead: boolean,
commission: number,
className ?: string
}
export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLead }: IAuctionCard) {
export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLead, commission, auctionId, className }: IAuctionCard) {
const [myBet, setBet] = useState(Number(myBetInit));
const [myNewBet, setMyNewBet] = useState(0);
const [initPrevBet, setPrevBet] = useState(prevBet);
@ -33,9 +35,10 @@ export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLea
const [closeAnim, setCloseAnim] = useState(false);
const [closeresultPopup, setCloseResultPopup] = useState(true);
const styleIndex = Number(localStorage.getItem('selectedStyle'));
const [closeErrorBet, setCloseErrorBet] = useState(true);
return (
<div className={`${styles.container} ${styleIndex===0 ? styles.darkContainer : styles.opacityContainer}`}>
<div className={`${styles.container} ${className} ${styleIndex===0 ? styles.darkContainer : styles.opacityContainer}`}>
<Slider className={styles.slider} imgs={imgs}/>
<h2 style={ETextStyles.InBd18120} className={styles.title}>{name}</h2>
<h3 style={ETextStyles.RwSb16120} className={styles.title2}>Подробности аукциона</h3>
@ -44,13 +47,13 @@ export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLea
<PointsBlock points={initPrevBet} sizeIcon={20}/>
</div>
<div className={`${styles.card} ${styles.cardFlex} ${styles.card2}`}>
<p style={ETextStyles.RwRg14100}>{users === 0 ? 'Ты единственный участник' : 'Участники аукциона'}</p>
{users === 0 ? <PersonIcon /> : <div className={styles.usersBlock}><UsersIcons/> {users > 3 && <div className={styles.userCount} style={ETextStyles.InSb10120}>{users-3}</div>}</div>}
<p style={ETextStyles.RwRg14100}>Количество победителей</p>
<div className={styles.winnersNumber}>{users}</div>
</div>
<div className={`${styles.card} ${initLead && styles.leadCard}`}>
<div className={styles.cardTop}>
<div className={styles.cardLeft} style={ETextStyles.RwSb14120}>{!initLead ? 'Успей сделать ставку! До конца осталось:'
: <p><span>Ты в числе победителей! </span>Но все может поменяться</p>}</div>
<div className={styles.cardLeft} style={ETextStyles.RwSb14120}>{initLead ? <p><span>Ты в числе победителей! </span>Но все может поменяться</p>
: <p>{myBet > 0 ? 'Вашу ставку перебили, повысьте ее, чтобы сохранить лидерство' : 'Успей сделать ставку! До конца осталось:' }</p> }</div>
<Timer initTime={time}/>
</div>
<Button onClick={() => setClose(false)} text={myBet === 0 ? 'Сделать первую ставку' : <div className={styles.newBtn}>
@ -63,11 +66,14 @@ export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLea
icon={EIcons.UpPriceIcon}/>
</div>
{!closeWindow && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setClose} removeBtn={true} modalBlock={
<AuctionPopup setLead={setLead} setClose={setCloseAnim} img={imgs[0]} name={name} prevBet={initPrevBet} prevUserImg={''} setBet={setMyNewBet} setCloseResultPopup={setCloseResultPopup}/>
<AuctionPopup myBet={myBet} setCloseErrorBet={setCloseErrorBet} auctionId={auctionId} commission={commission} setLead={setLead} setClose={setCloseAnim} img={imgs[0]} name={name} prevBet={initPrevBet} prevUserImg={''} setBet={setMyNewBet} setCloseResultPopup={setCloseResultPopup}/>
} />}
{!closeresultPopup && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseResultPopup} removeBtn={true} modalBlock={
{!closeresultPopup && closeErrorBet && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseResultPopup} removeBtn={true} modalBlock={
<ResultAuctionPopup prevBet={initPrevBet} prevMyBet={myBet} newBet={myNewBet} setBet={setBet} setClose={setCloseAnim} setCloseBetWindow={setClose} setPrevBet={setPrevBet}/>
} />}
{!closeErrorBet && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseErrorBet} removeBtn={true} modalBlock={
<DevPopup setClose={setCloseAnim} title='Возникла ошибка' text='Не получилось сделать ставку. Но мы скоро всё починим.' />
} />}
</div>
);
}

View File

@ -114,3 +114,13 @@
border-radius: 15px;
overflow: hidden;
}
.winnersNumber {
display: flex;
align-items: center;
justify-content: center;
width: 27px;
height: 27px;
border-radius: 50%;
background-color: var(--grey34);
}

View File

@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
import { ETextStyles } from '../../texts';
import { ProductCard } from '../ProductCard';
import { Button } from '../../Button';
import { generateRandomString } from '../../../utils/generateRandom';
interface IProduct {
name: string,
@ -28,7 +29,7 @@ export function AuctionLosePopup({ items, setClose }: IAuctionLosePopup) {
<h3 className={styles.title2} style={ETextStyles.RwSb18120}>Аукционы, в&nbsp;которых нужно увеличить ставку:</h3>
<div className={styles.cards}>
{items.map(item => {
return <ProductCard name={item.name} img={item.img} bet={item.bet} />
return <ProductCard key={ generateRandomString() } name={item.name} img={item.img} bet={item.bet} />
})}
</div>
<Button text='Увеличить ставку' onClick={() => { navigate('/auction'); setClose(true) }} />

View File

@ -1,39 +1,95 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import styles from './auctionmainpopups.module.css';
import { ModalWindow } from '../../ModalWindow';
import { AuctionWinPopup } from '../AuctionWinPopup';
import { AuctionTopPopup } from '../AuctionTopPopup';
import { AuctionLosePopup } from '../AuctionLosePopup';
import { useAppSelector } from '../../hooks/useAppSelector';
import { IAuctionItem } from '../../../store/me/actions';
export function AuctionMainPopups() {
const [closeWin, setCloseWin] = useState(true);
const [closeTop, setCloseTop] = useState(true);
const [closeLose, setCloseLose] = useState(true);
const [closeAnim, setCloseAnim] = useState(false);
const topAuctions = useAppSelector<Array<IAuctionItem> | undefined>(state=>state.me.data.topAuctions);
const loseAuctions = useAppSelector<Array<IAuctionItem> | undefined>(state => state.me.data.loseAuctions);
const winAuctions = useAppSelector<Array<IAuctionItem> | undefined>(state => state.me.data.winAuctions);
const [winInfo, setWinInfo] = useState<IAuctionItem>();
const items = [
{
name: 'iPhone 15 Pro Max',
img: '',
bet: '788'
},
{
name: 'iPhone 13 Pro',
img: '',
bet: '200'
useEffect(() => {
let showWindow = false;
if (winAuctions && winAuctions.length != 0) {
for (let i = 0; i < winAuctions.length; i++) {
const winShow = localStorage.getItem('wS');
if (winShow) {
const winArray = JSON.parse(winShow);
if (winArray && winArray.length != 0) {
let isExist = false;
for (let k = 0; k < winArray.length; k++) {
if (Number(winArray[k]) === Number(winAuctions[i].id)) {
isExist = true;
}
}
if(!isExist) {
winArray.push(winAuctions[i].id);
localStorage.setItem('wS', JSON.stringify(winArray));
showWindow = true;
setWinInfo(winAuctions[i]);
}
} else {
const newArray = [];
newArray.push(winAuctions[i].id);
localStorage.setItem('wS', JSON.stringify(newArray));
showWindow = true;
setWinInfo(winAuctions[i]);
}
} else {
const newArray = [];
newArray.push(winAuctions[i].id);
localStorage.setItem('wS', JSON.stringify(newArray));
showWindow = true;
setWinInfo(winAuctions[i]);
}
}
}
];
if(showWindow) {
setCloseWin(false);
}
}, [winAuctions]);
useEffect(() => {
const show = sessionStorage.getItem('shT');
if (show === 't' && closeTop) {
if (topAuctions && topAuctions.length != 0) {
sessionStorage.setItem('shT', 'f');
setCloseTop(false);
}
}
}, [topAuctions]);
useEffect(() => {
const show = sessionStorage.getItem('shL');
if (show === 't' && closeLose) {
if (loseAuctions && loseAuctions.length != 0) {
sessionStorage.setItem('shL', 'f');
setCloseLose(false);
}
}
}, [loseAuctions]);
return (
<div>
{!closeWin && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseWin} removeBtn={true} modalBlock={
<AuctionWinPopup name='iPhone 15 Pro Max ' img='' setClose={setCloseAnim}/>
<AuctionWinPopup name={winInfo?.name ? winInfo?.name : ''} img={winInfo?.img ? winInfo?.img : ''} setClose={setCloseAnim}/>
} />}
{!closeTop && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseTop} removeBtn={true} modalBlock={
<AuctionTopPopup items={items} setClose={setCloseAnim}/>
{!closeTop && topAuctions != undefined && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseTop} removeBtn={true} modalBlock={
<AuctionTopPopup items={topAuctions} setClose={setCloseAnim}/>
} />}
{!closeLose && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseLose} removeBtn={true} modalBlock={
<AuctionLosePopup items={items} setClose={setCloseAnim} />
{!closeLose && loseAuctions != undefined && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseLose} removeBtn={true} modalBlock={
<AuctionLosePopup items={loseAuctions} setClose={setCloseAnim} />
} />}
</div>
);

View File

@ -1,15 +1,19 @@
import React, { useState } from 'react';
import styles from './auctionpopup.module.css';
import { ETextStyles } from '../../texts';
import { PersonIcon } from '../../Elements/PersonIcon';
import { PointsBlock } from '../../Elements/PointsBlock';
import { Button } from '../../Button';
import { EIcons } from '../../Icons';
import { declension } from '../../../utils/declension';
import { useNavigate } from 'react-router-dom';
import { ProductCard } from '../ProductCard';
import { useAppSelector } from '../../hooks/useAppSelector';
import { useDispatch } from 'react-redux';
import axios from 'axios';
import { updatePointsRequestAsync } from '../../../store/me/actions';
import { updateAuction } from '../../../store/auction/actions';
interface IAuctionPopup {
auctionId: string,
setClose(a: boolean): void,
setLead(a: boolean): void,
img: string,
@ -17,16 +21,23 @@ interface IAuctionPopup {
prevBet: string,
prevUserImg: string,
setBet(a: number): void,
setCloseResultPopup(a: boolean): void
setCloseResultPopup(a: boolean): void,
commission: number,
setCloseErrorBet(a: boolean): void,
myBet: number
}
export function AuctionPopup({ setClose, img, name, prevBet, prevUserImg, setBet, setLead, setCloseResultPopup }: IAuctionPopup) {
export function AuctionPopup({ setClose, setCloseErrorBet, auctionId, img, name, prevBet, prevUserImg, setBet, setLead, setCloseResultPopup, commission, myBet }: IAuctionPopup) {
const [value, setValue] = useState<string>('');
const [disabled, setDis] = useState(true);
const [autoBet, setAutoBet] = useState(false);
const navigate = useNavigate();
const percent = 5;
const userPoints = 1000;
const [percent, setPercent] = useState(commission);
const userPoints = Number(useAppSelector<string | undefined>(state=>state.me.data.points));
const URL = useAppSelector<string>(state=>state.url);
const token = useAppSelector<string>(state => state.token);
const dispatch = useDispatch();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = event.target.value;
@ -41,28 +52,43 @@ export function AuctionPopup({ setClose, img, name, prevBet, prevUserImg, setBet
}
const newBet = () => {
setBet(Math.floor((1 + percent / 100) * Number(value)));
const bet = Number(value);
setClose(true);
setLead(true);
const timer = setInterval(() => {
setCloseResultPopup(false);
clearTimeout(timer);
}, 400);
if (token) {
axios.post(`${URL}/api/v1/auction/auction/${auctionId}/place-bet/?value=${bet}`, {},
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data;
dispatch<any>(updatePointsRequestAsync());
dispatch<any>(updateAuction(auctionId));
setBet(bet);
//setLead(true);
const timer = setInterval(() => {
setCloseResultPopup(false);
clearTimeout(timer);
}, 400);
}).catch(err => {
setCloseErrorBet(false);
})
}
};
return (
<div>
<h2 className={styles.title} style={ETextStyles.RwSb24100}>Сделать ставку</h2>
<ProductCard name={name} img={img} bet={prevBet} personImg={prevUserImg} className={styles.card} />
{!autoBet ? <Button onClick={() => { setAutoBet(true), setValue((Number(prevBet) + 5).toString()), setDis(false) }} text='Сразу перебить ставку' className={styles.btnFirst} icon={<div className={styles.icon} style={{ backgroundImage: "url('assets/Rocket.png')" }}></div>} /> :
{!autoBet ? <Button onClick={() => { setAutoBet(true), setValue(Number(Number((1 + percent / 100) * Number(prevBet)).toFixed(2)).toString()), setDis(false) }} text='Сразу перебить ставку' className={styles.btnFirst} icon={<div className={styles.icon} style={{ backgroundImage: "url('assets/Rocket.png')" }}></div>} /> :
<button style={ETextStyles.InBd14120} className={styles.btnCancel} onClick={() => setClose(true)}>Не перебивать</button>
}
<p className={styles.descr} style={ETextStyles.RwRg10140}>Наши алгоритмы автоматически рассчитают стоимость, чтобы ваша ставка стала самой высокой</p>
<h3 className={styles.title2} style={ETextStyles.InSb14120}>{!autoBet ? 'Ввести свою цену' : 'Цена, чтобы перебить ставку'}</h3>
<input style={ETextStyles.InSb14120} className={styles.input} value={value} type="text" onChange={handleChange} inputMode="numeric" />
{(Math.floor((1 + percent / 100) * Number(value)) < userPoints) ? ((Number(value) < Number(prevBet) && value.length > 0) ?
{(Number(Number((1 + percent / 100) * Number(value))) - myBet < userPoints) ? ((Number(value) < Number(prevBet) && value.length > 0) ?
<button className={styles.btnForbidden}>
<p style={ETextStyles.InBd14120}>Ставка должна быть больше</p>
<p style={ETextStyles.InRg12140} className={styles.textForbidden}>Нельзя сделать ставку меньше предыдущей</p>
@ -70,7 +96,7 @@ export function AuctionPopup({ setClose, img, name, prevBet, prevUserImg, setBet
: <Button onClick={() => newBet()} disabled={disabled} text={disabled ? 'Перебить ставку' : <div className={styles.newBtn}>
<p>Перебить ставку</p>
<div className={styles.btnText}>
<p style={ETextStyles.InRg12140}>{`${declension(value, 'коин', 'коина', 'коинов', true)} + ${percent}% = ${declension(Math.floor((1 + percent / 100) * Number(value)), 'коин', 'коина', 'коинов', true)}`}</p>
<p style={ETextStyles.InRg12140}>{`${declension(value, 'коин', 'коина', 'коинов', true)} + ${percent}% = ${declension(Number(Number((1 + percent / 100) * Number(value)).toFixed(2)), 'коин', 'коина', 'коинов', true)}`}</p>
<div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')" }}></div>
</div>
</div>}

View File

@ -4,6 +4,7 @@ import { ETextStyles } from '../../texts';
import { ProductCard } from '../ProductCard';
import { Button } from '../../Button';
import { useNavigate } from 'react-router-dom';
import { generateRandomString } from '../../../utils/generateRandom';
interface IProduct {
name: string,
@ -20,7 +21,7 @@ export function AuctionTopPopup({ items, setClose }: IAuctionTopPopup) {
const navigate = useNavigate();
return (
<div>
<div className='top'>
<div className={styles.iconBlock}>
<div className={styles.icon} style={{ backgroundImage: "url('assets/Fire.png')" }}></div>
</div>
@ -29,7 +30,7 @@ export function AuctionTopPopup({ items, setClose }: IAuctionTopPopup) {
<h3 className={styles.title2} style={ETextStyles.RwSb18120}>Аукционы, в которых вы лидируете:</h3>
<div className={styles.cards}>
{items.map(item => {
return <ProductCard name={item.name} img={item.img} bet={item.bet} />
return <ProductCard key={ generateRandomString() } name={item.name} img={item.img} bet={item.bet} />
})}
</div>
<Button text='Продолжить кликать' onClick={() => { navigate('/'); setClose(true)}}/>

View File

@ -18,12 +18,17 @@ interface IResultAuctionPopup {
export function ResultAuctionPopup({ prevBet, prevMyBet, newBet, setBet, setClose, setCloseBetWindow, setPrevBet }: IResultAuctionPopup) {
const [diff, setDiff] = useState(0);
const [prevBetOld, setPrevBetOld] = useState(prevBet);
const [prevMyOldBet, setPrevMyOld] = useState(prevMyBet);
useEffect(() => {
setDiff(newBet - prevMyBet);
if (prevMyOldBet > 0) {
setDiff(newBet - prevMyOldBet);
} else {
setDiff(newBet - Number(prevBetOld));
}
setBet(newBet);
setPrevBet(newBet.toString())
setPrevBet(newBet.toString());
}, []);
const onClick = () => {
@ -40,7 +45,7 @@ export function ResultAuctionPopup({ prevBet, prevMyBet, newBet, setBet, setClos
<div className={styles.icon} style={{backgroundImage: 'url("assets/Money.png")'}}></div>
</div>
<div className={styles.text} style={ETextStyles.InSb24100}>
Вы <span>увеличили</span> ставку <span>{`на ${diff}`}</span> {declension(diff, 'коин', 'коина', 'коинов')}
Вы <span>{prevMyOldBet > 0 ? 'увеличили' : 'перебили'}</span> ставку <span>{`на ${diff.toFixed(2)}`}</span> {declension(diff.toFixed(2), 'коин', 'коина', 'коинов')}
</div>
<div className={styles.cards}>
<div className={styles.card}>

View File

@ -16,6 +16,7 @@ export function Timer({initTime}: ITimer) {
const tick = () => {
if (hour === 0 && min === 0 && sec === 0) {
setOver(true);
window.location.reload();
} else if (min === 0 && sec === 0) {
setTime([hour - 1, 59, 59]);
} else if (sec == 0) {
@ -35,12 +36,12 @@ export function Timer({initTime}: ITimer) {
return (
<div className={styles.container}>
<div className={styles.block}>
<p className={styles.value} style={ETextStyles.InSb14120}>{hour}</p>
<p className={styles.value} style={ETextStyles.InSb14120}>{hour.toString().length === 1 ? `0${hour}` : hour}</p>
<p className={styles.text} style={ETextStyles.RwRg12120}>{declension(hour, 'час', 'часа', 'часов')}</p>
</div>
<div className={styles.dot}></div>
<div className={styles.block}>
<p className={styles.value} style={ETextStyles.InSb14120}>{min}</p>
<p className={styles.value} style={ETextStyles.InSb14120}>{min.toString().length === 1 ? `0${min}` : min}</p>
<p className={styles.text} style={ETextStyles.RwRg12120}>мин</p>
</div>
<div className={styles.dot}></div>

View File

@ -1,27 +1,27 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import styles from './clickerbtn.module.css';
import { ModalWindow } from '../../ModalWindow';
import { ClickerPopup } from '../ClickerPopup';
import { getGradient } from '../../../utils/getGradient';
import { useAppSelector } from '../../hooks/useAppSelector';
import { useDispatch } from 'react-redux';
import { IUserData, updateEnergyRequestAsync } from '../../../store/me/actions';
import axios from 'axios';
import { DevPopup } from '../../Elements/DevPopup';
import { saveMult } from '../../../store/mult';
import { Spinner } from '../../Elements/Spinner';
import { updateEnergyRequestAsync } from '../../../store/me/actions';
interface IClickerBtn {
coins: number,
setCoins(a: number): void,
energy: number,
setMult(a: number): void,
setEnergy(a: number): void
setEnergy(a: number): void,
setClickTime(a: number): void,
clickTime: number,
sameCoords: boolean,
setSameCoords(a: boolean): void,
closeAutoClick: boolean,
setSameInterval(a: boolean): void,
}
export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: IClickerBtn) {
const urlClick = useAppSelector<string>(state => state.urlClick);
const token = useAppSelector<string>(state => state.token);
export function ClickerBtn({ setCoins, setSameInterval, closeAutoClick, energy, setMult, coins, setEnergy, setClickTime, clickTime, setSameCoords }: IClickerBtn) {
const [fill, setFill] = useState(0);
const [size, setSize] = useState(240);
const circumference = 2 * Math.PI * 125;
@ -32,6 +32,23 @@ export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: ICli
let styleIndex = useAppSelector<number>(state => state.styleIndex);
const [maxEnergy, setMaxEnergy] = useState(500);
const dispatch = useDispatch();
const [prevClickTime, setPrevClickTime] = useState(0);
const [prevCoords, setPrevCoords] = useState(0);
const [clickInterval, setClickInterval] = useState(0);
useEffect(() => {
if(!closeAutoClick) {
setClose(true);
}
}, [closeAutoClick]);
useEffect(() => {
if(clickTime === 0) {
setPrevClickTime(0);
setPrevCoords(0);
}
}, [clickTime]);
useEffect(() => {
setFill((maxEnergy - energy) / maxEnergy * 100);
@ -50,7 +67,33 @@ export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: ICli
setGradient(getGradient())
}, [styleIndex]);
const btnClick = () => {
const btnClick = (event: React.MouseEvent<HTMLDivElement>) => {
const x = event.nativeEvent.offsetX;
const y = event.nativeEvent.offsetY;
const coords = x*y;
if (coords === prevCoords) {
setSameCoords(true);
} else {
setSameCoords(false);
}
setPrevCoords(coords);
const currentTime = Date.now();
const clickIntervalInit = currentTime - prevClickTime;
if (clickInterval != 0) {
if (clickInterval === clickIntervalInit) {
setSameInterval(true)
} else {
setSameInterval(false)
}
}
if(prevClickTime != 0) {
setClickTime(clickTime + clickIntervalInit);
}
setPrevClickTime(currentTime);
if (energy != 0) {
const newEnergy = energy - 1;
const newFill = (maxEnergy - newEnergy) / maxEnergy * 100;
@ -70,70 +113,11 @@ export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: ICli
} else {
setClose(false);
}
//sendClick();
} else {
setClose(false);
}
};
/*const sendClick = () => {
if (token && !loading) {
setLoading(true);
axios.post(`${urlClick}/api/v1/click/`,
{},
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then((resp) => {
//console.log(resp);
if(resp.data) {
setLoading(false);
const click = Number(resp.data.click.value);
//
const encodeMult = btoa(click.toString());
sessionStorage.setItem('mt', encodeMult);
//
const newEnergy = Number(resp.data.energy);
setMult(Number(click.toFixed(2)))
dispatch<any>(saveMult(Number(click.toFixed(2))));
const newFill = (maxEnergy - newEnergy) / maxEnergy * 100;
if (newFill <= 100) {
const newCoins = Number(coins + click);
dispatch<any>(updateEnergyRequestAsync(newEnergy))
setCoins(newCoins);
setEnergy(newEnergy)
setFill(newFill);
} else {
setFill(100);
}
if (newFill < 100) {
setSize(220);
const timer = setTimeout(() => {
setSize(240);
clearTimeout(timer);
}, 100);
} else {
setClose(false);
}
//
}
if(error) {
setError(false)
}
}).catch((err) => {
setLoading(false);
setCloseError(false);
setError(true);
console.log(err);
})
}
};*/
const hotCards = [
{
title: 'Ты большой молодец',
@ -162,7 +146,7 @@ export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: ICli
{gradient}
</defs>
</svg>
{!close && <ModalWindow setCloseAnimOut={setClose} removeBtn={true} setClose={setClose} modalBlock={
{!close && closeAutoClick && <ModalWindow setCloseAnimOut={setClose} removeBtn={true} setClose={setClose} modalBlock={
<ClickerPopup title='Кнопка перегрелась' cards={hotCards} setClose={setClose} isBtn={true}/>
} />}
</div>

View File

@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom';
export function ClickerFooter() {
const navigate = useNavigate();
const isDev = true;
const [isDev, setIsDev] = useState(false);
return (
<div className={styles.container}>

View File

@ -4,64 +4,105 @@ import { formatNumber } from '../../../utils/formatNumber';
import { ETextStyles } from '../../texts';
import ReactDOM from 'react-dom';
import { useDispatch } from 'react-redux';
import { meRequest, meRequestError, updateEnergyRequestAsync, updatePointsRequestAsync } from '../../../store/me/actions';
import { IUserData, meRequest, meRequestSuccess, updateEnergyRequestAsync, updatePointsRequestAsync } from '../../../store/me/actions';
import { checkIOS } from '../../../utils/checkMobile';
import axios from 'axios';
import { useAppSelector } from '../../hooks/useAppSelector';
import { saveMult } from '../../../store/mult';
import { sendAutoClickData } from '../../hooks/sendAutoClickData';
interface IPointsZoom {
points: number,
setClose(a:boolean): void,
className ?: string,
setClose(a: boolean): void,
className?: string,
closePointsAnim: boolean,
setClosePointsAnim(a: boolean): void,
setCoins(a:number):void,
setCoins(a: number): void,
setCloseError(a: boolean): void,
setEnergy(a: number): void,
setMult(a: number): void,
setClickTime(a: number): void,
clickTime: number,
setCloseAutoClick(a: boolean): void,
sameCoords: boolean,
setSameCoords(a: boolean): void,
setSameInterval(a: boolean): void,
sameInterval: boolean,
}
export function PointsZoom({ points, setMult, setClose, setCoins, className, closePointsAnim, setClosePointsAnim, setCloseError, setEnergy }: IPointsZoom) {
export function PointsZoom({ points, sameInterval, setSameInterval, sameCoords, setSameCoords, setCloseAutoClick, setMult, setClose, setCoins, className, closePointsAnim, setClosePointsAnim, setCloseError, setEnergy, clickTime, setClickTime }: IPointsZoom) {
const [open, setOpen] = useState(true);
const node = document.querySelector('#modal_root');
const urlClick = useAppSelector<string>(state => state.urlClick);
const token = useAppSelector<string>(state => state.token);
const [sizeHand, setSizeHand] = useState(30);
const energy = Number(useAppSelector<string | undefined>(state=>state.me.data.energy));
const energy = Number(useAppSelector<string | undefined>(state => state.me.data.energy));
const userData = useAppSelector<IUserData>(state => state.me.data);
const URL = useAppSelector<string>(state => state.url);
if (!node) return null;
const dispatch = useDispatch();
const sendClicks = () => {
const initPoints = points;
dispatch(meRequest());
axios.post(`${urlClick}/api/v1/batch-click/`,
{
count: initPoints
},
{
const clickTimeInit = clickTime;
let initSameCoords = sameCoords;
let initSameInterval = sameInterval;
let avtTime = 500;
if (points > 1) {
avtTime = clickTimeInit / initPoints;
}
setClickTime(0);
setSameCoords(false);
if ((avtTime < 100 && initPoints > 20) || (avtTime < 130 && initPoints > 300)) {
axios.post(`${URL}/api/v1/users/warn/`, {}, {
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data;
const click = Number(data.click.value);
const encodeMult = btoa(click.toString());
sessionStorage.setItem('mt', encodeMult);
setMult(Number(click.toFixed(2)));
const energy = Number(data.energy);
dispatch<any>(saveMult(Number(click.toFixed(2))));
dispatch<any>(updateEnergyRequestAsync(energy));
dispatch<any>(updatePointsRequestAsync());
}).catch(err => {
console.log(err);
setCloseError(false);
}).then(resp => {
//console.log(resp);
}).catch(err => {
//console.log(err)
})
//sendAutoClickData(userData.tgId, points, avtTime);
setCloseAutoClick(false);
setSameInterval(false);
const returnEnergy = energy + initPoints;
setEnergy(returnEnergy);
dispatch<any>(updateEnergyRequestAsync(returnEnergy));
dispatch(meRequestError(String(err)));
})
} else {
dispatch(meRequest());
axios.post(`${urlClick}/api/v1/batch-click/`,
{
count: initPoints
},
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data;
const click = Number(data.click.value);
const encodeMult = btoa(click.toString());
sessionStorage.setItem('mt', encodeMult);
setMult(Number(click.toFixed(2)));
const energy = Number(data.energy);
dispatch<any>(saveMult(Number(click.toFixed(2))));
dispatch<any>(updateEnergyRequestAsync(energy));
dispatch<any>(updatePointsRequestAsync());
}).catch(err => {
console.log(err);
setCloseError(false);
const returnEnergy = energy + initPoints;
setEnergy(returnEnergy);
dispatch<any>(updateEnergyRequestAsync(returnEnergy));
dispatch(meRequestSuccess(userData));
})
}
};
useEffect(() => {

View File

@ -9,6 +9,7 @@ import { useNavigate } from 'react-router-dom';
import { UsersIcons } from '../../Elements/UsersIcons';
import { formatNumber } from '../../../utils/formatNumber';
import { useAppSelector } from '../../hooks/useAppSelector';
import { IUserRank } from '../../../store/friends/actions';
interface ISectionsBlock {
mult:number;
@ -18,9 +19,25 @@ export function SectionsBlock({ mult }: ISectionsBlock) {
const [close, setClose] = useState(true);
const navigate = useNavigate();
const referralStorage = Number(useAppSelector<string | undefined>(state => state.me.data.referralStorage));
//const referralStorage = 500;
const maxReferralStorage = useAppSelector<number>(state => state.me.data.maxStorage);
const [referralPercent, serReferralPercent] = useState(0);
const [isDev, setIsDev] = useState(false);
const userRank = useAppSelector<number | undefined>(state => state.me.data.rank);
const rankData = useAppSelector<Array<IUserRank>>(state => state.rank.data);
const [topImgs, setTopImgs] = useState<Array<string>>([]);
useEffect(() => {
const imgs:Array<string> = [];
if(rankData.length != 0) {
for (let i = 0; i < rankData.length; i++) {
if (i < 3 && rankData[i].avatar) {
//@ts-ignore
imgs.push(rankData[i].avatar);
}
}
setTopImgs(imgs);
}
}, [rankData]);
useEffect(() => {
if(referralStorage >= maxReferralStorage) {
@ -31,8 +48,6 @@ export function SectionsBlock({ mult }: ISectionsBlock) {
}, [referralStorage, maxReferralStorage]);
const isDev = true;
const multipCards = [
{
title: 'Что он делает',
@ -51,16 +66,17 @@ export function SectionsBlock({ mult }: ISectionsBlock) {
},
];
//<UsersIcons imgs={topImgs} size={16}/>
return (
<div className={styles.sectionContainer}>
<div className={styles.leftContainer}>
<CardSection title='Место в топе' onClick={() => {!isDev ? navigate('/rating') : navigate('/dev?type=rating')}}>
<CardSection title='Место в топе' onClick={() => { !isDev ? navigate('/rating') : navigate('/dev?type=rating') }}>
{<div className={`${styles.bottomRank} ${isDev ? styles.dev : ''}`}>
<div style={ETextStyles.InSb12120}>
<span className={styles.rank1}>#</span>
<span>{formatNumber(1)}</span>
<span>{isDev ? '?' : (userRank ? formatNumber(userRank) : '?')}</span>
</div>
<UsersIcons size={16}/>
</div>}
</CardSection>
<CardSection title='Множитель' onClick={() => { setClose(false) }}>

View File

@ -7,13 +7,14 @@ interface IDevPopup {
setClose(a: boolean): void
title: string,
text: string,
img?: string,
}
export function DevPopup({ setClose, title, text }: IDevPopup) {
export function DevPopup({ setClose, title, text, img }: IDevPopup) {
return (
<div className={styles.container}>
<div className={styles.iconContainer}>
<div className={styles.icon} style={{backgroundImage: "url('assets/programming.gif')"}}></div>
<div className={styles.icon} style={{ backgroundImage: `url(${img ? img : 'assets/programming.gif'})`}}></div>
</div>
<h2 className={styles.title} style={ETextStyles.RwSb24100}>{title}</h2>
<p className={styles.text} style={ETextStyles.RwSb14120}>{text}</p>

View File

@ -6,10 +6,13 @@ interface IPersonIcon {
img ?: string,
className?: string,
left?: number,
letter?: string,
}
export function PersonIcon({ size = 25, img = '', className, left=0 }: IPersonIcon) {
export function PersonIcon({ size = 25, img = '', className, left=0, letter }: IPersonIcon) {
return (
<div className={`${styles.container} ${className}`} style={{width: `${size}px`, height: `${size}px`, backgroundImage: `url(${img})`, left: `${left}px`}}></div>
<div className={`${styles.container} ${className}`} style={{width: `${size}px`, height: `${size}px`, backgroundImage: `url(${img})`, left: `${left}px`}}>
{img.length === 0 && <span className={styles.letter}>{letter?.toUpperCase()}</span> }
</div>
);
}

View File

@ -3,5 +3,13 @@
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-color: var(--white);
background-color: var(--grey35);
display: flex;
align-items: center;
justify-content: center;
}
.letter {
color: var(--primary);
font-weight: 700;
}

View File

@ -3,28 +3,36 @@ import styles from './ratingcard.module.css';
import { EIcons, Icon } from '../../Icons';
import { PointsBlock } from '../PointsBlock';
import { ETextStyles } from '../../texts';
import { PersonIcon } from '../PersonIcon';
interface IRatingCard {
number: number,
name: string,
score: string
score: string,
index: number,
friend ?: boolean,
img: string
}
export function RatingCard({number, name, score}: IRatingCard) {
export function RatingCard({number, name, score, index, friend=false, img}: IRatingCard) {
let order = number;
if(friend) {
order = index;
}
return (
<div className={`${styles.container} ${number < 4 && styles.win} ${name==='Ты' && styles.you}`}>
<div className={`${styles.container} ${(order < 4) && styles.win} ${name==='Ты' && styles.you}`}>
<div className={styles.left}>
{(number === 1) && <div className={styles.medal}>
{order === 1 && <div className={styles.medal}>
<Icon icon={EIcons.MedalFirst}/>
</div>}
{(number === 2) && <div className={styles.medal}>
{order === 2 && <div className={styles.medal}>
<Icon icon={EIcons.MedalSecond} />
</div>}
{(number === 3) && <div className={styles.medal}>
{order === 3 && <div className={styles.medal}>
<Icon icon={EIcons.MedalThird} />
</div>}
{(number > 3) && <div className={styles.number} style={ETextStyles.InSb14120}>{number}</div>}
<div className={styles.img}></div>
{(order > 3 ) && <div className={styles.number} style={ETextStyles.InSb14120}>{order}</div>}
<PersonIcon size={20} img={img} className={styles.img} letter={name[0]}/>
<p style={ETextStyles.RwSb14120} className={styles.name}>{name}</p>
</div>
<PointsBlock points={score}/>

View File

@ -21,10 +21,10 @@
.img {
margin-right: 4px;
width: 20px;
/*width: 20px;
height: 20px;
border-radius: 20px;
background-color: var(--white);
background-color: var(--white);*/
}
.name {

View File

@ -1,6 +1,7 @@
import React from 'react';
import styles from './usersicons.module.css';
import { PersonIcon } from '../PersonIcon';
import { EIcons, Icon } from '../../Icons';
interface IUsersIcons {
size?: number,
@ -11,9 +12,15 @@ interface IUsersIcons {
export function UsersIcons({ size = 25, imgs = [], className = '' }: IUsersIcons) {
return (
<div className={`${styles.users} ${className}`} style={{height: `${size}px`, width: `${size*2.5}px`}}>
<PersonIcon className={`${styles.userIcon} ${styles.userIcon1}`} size={size}/>
<PersonIcon className={`${styles.userIcon} ${styles.userIcon2}`} size={size} left={size/1.4}/>
<PersonIcon className={`${styles.userIcon} ${styles.userIcon3}`} size={size} left={2*size / 1.4} />
<div className={`${styles.userIcon} ${styles.userIcon1}`}>
<Icon icon={EIcons.MedalFirst}/>
</div>
<div className={`${styles.userIcon} ${styles.userIcon2}`}>
<Icon icon={EIcons.MedalSecond} />
</div>
<div className={`${styles.userIcon} ${styles.userIcon3}`}>
<Icon icon={EIcons.MedalThird} />
</div>
</div>
);
}

View File

@ -3,11 +3,14 @@
}
.userIcon {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
border-radius: 50%;
background-color: var(--grey6C);
border: 1px solid var(--grey12);
width: 20px;
height: 20px;
opacity: 0.8;
}
.userIcon1 {
@ -17,8 +20,10 @@
.userIcon2 {
z-index: 2;
left: 14px;
}
.userIcon3 {
z-index: 1;
left: 28px;
}

View File

@ -10,9 +10,10 @@ interface IModalWindow {
removeBtn ?: boolean,
closeAnimOut?: boolean,
setCloseAnimOut(a: boolean): void,
isReload?: boolean,
}
export function ModalWindow({ modalBlock, setClose, removeBtn, closeAnimOut, setCloseAnimOut }: IModalWindow) {
export function ModalWindow({ modalBlock, setClose, removeBtn, closeAnimOut, setCloseAnimOut, isReload=false }: IModalWindow) {
const node = document.querySelector('#modal_root');
const [closeAnim, setCloseAnim] = useState(false);
const html = document.querySelector('html');
@ -34,6 +35,9 @@ export function ModalWindow({ modalBlock, setClose, removeBtn, closeAnimOut, set
const timer = setTimeout(() => {
setClose(true);
clearTimeout(timer);
if(isReload) {
window.location.reload();
}
}, 400);
}

View File

@ -1,17 +1,41 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import styles from './auctionpage.module.css';
import { ETextStyles } from '../../texts';
import { AuctionCard } from '../../Auction/AuctionCard';
import { useAuctionData } from '../../hooks/useAuctionData';
import { Spinner } from '../../Elements/Spinner';
import { ErrorPage } from '../ErrorPage';
export function AuctionPage() {
const imgs = ['https://cdn.dribbble.com/userupload/11863775/file/original-6009708366fadd352f61fbaf0db5acee.png?resize=1200x853',
'https://cdn.dribbble.com/userupload/10040892/file/original-850d482568c1f1c870b7066113903bd2.png?resize=1200x900',
'https://cdn.dribbble.com/users/9735273/screenshots/19338580/media/6657322ea7990bd504427ed1b171be3d.png?resize=1200x900']
const { dataAuction, loadingAuction, errorAuction } = useAuctionData();
const [auctionBlock, setAuctionBlock] = useState( <div></div> );
useEffect(() => {
if(dataAuction.length != 0) {
const newBlock = dataAuction.map(item => {
if (item.productName && item.productCover && item.initialCost && item.time && item.winnersNumber && item.commission && item.id && item.isLead != undefined && item.myBet != undefined)
return <AuctionCard className={styles.card} auctionId={item.id} key={`${item.id}${JSON.stringify(dataAuction)}`} name={item.productName} imgs={[item.productCover]} users={item.winnersNumber} prevBet={item.initialCost} myBetInit={item.myBet} time={item.time} isLead={item.isLead} commission={item.commission}/>
});
//@ts-ignore
setAuctionBlock(newBlock);
}
}, [dataAuction]);
return (
<div>
<h1 className={styles.title} style={ETextStyles.RwSb26100}> <span>Соревнуйся за товары</span> на аукционе!</h1>
<AuctionCard name='iPhone 15 Pro Max, 256gb, Natural Titanium' imgs={imgs} minBet='200' users={23} prevBet='290' myBetInit='0' time={86400} isLead={false}/>
{loadingAuction && <div className={styles.spinnerContainer}><Spinner color='#FFFFFF' size='40px' thickness='6px' className={styles.spinner} /></div>}
{!loadingAuction && <div>
{errorAuction ? <ErrorPage fullScreen={true} title='Возникла ошибка загрузки аукционов' text='Перезагрузите страницу или попробуйте позже' detail={errorAuction} /> :
<div>
<h1 className={styles.title} style={ETextStyles.RwSb26100}> <span>Соревнуйся за товары</span> на аукционе!</h1>
{dataAuction.length != 0 ? auctionBlock
: <p style={ETextStyles.InRg14120}>Скоро тут появятся новые аукционы.</p>
}
</div>
}
</div>
}
</div>
);
}

View File

@ -5,3 +5,15 @@
.title span {
color: var(--primary);
}
.spinnerContainer {
display: flex;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
}
.card {
margin-bottom: 24px;
}

View File

@ -11,6 +11,8 @@ import { useWindowSize } from 'usehooks-ts';
import { useAppSelector } from '../../hooks/useAppSelector';
import { ModalWindow } from '../../ModalWindow';
import { DevPopup } from '../../Elements/DevPopup';
import { AuctionMainPopups } from '../../Auction/AuctionMainPopups';
import { useAuctionData } from '../../hooks/useAuctionData';
interface IClickerPageInterface {
name: string,
@ -30,6 +32,19 @@ export function ClickerPage({ name, points, img, energy }: IClickerPageInterface
const [closeError, setCloseError] = useState(true);
const [animClose, setAnimClose] = useState(false);
const [initEnergy, setInitEnergy] = useState(energy);
const [clickTime, setClickTime] = useState(0);
const [closeAutoClick, setCloseAutoClick] = useState(true);
const [sameCoords, setSameCoords] = useState(false);
const [sameInterval, setSameInterval] = useState(false);
useAuctionData();
useEffect(() => {
const html = document.querySelector('html');
if(html) {
html.style.overflowY = 'scroll';
}
}, []);
useEffect(() => {
setMult(savedMult);
@ -54,13 +69,13 @@ export function ClickerPage({ name, points, img, energy }: IClickerPageInterface
return (
<div className={styles.container}>
<div className={styles.records}>
{!closePoints && <PointsZoom setMult={setMult} setEnergy={setInitEnergy} setCloseError={setCloseError} setCoins={setCoins} points={coins} setClosePointsAnim={setClosePointsAnim} setClose={setClosePoints} className={styles.pointsAnim} closePointsAnim={closePointsAnim}/>}
{!closePoints && <PointsZoom sameInterval={sameInterval} setSameInterval={setSameInterval} sameCoords={sameCoords} setSameCoords={setSameCoords} setCloseAutoClick={setCloseAutoClick} setClickTime={setClickTime} clickTime={clickTime} setMult={setMult} setEnergy={setInitEnergy} setCloseError={setCloseError} setCoins={setCoins} points={coins} setClosePointsAnim={setClosePointsAnim} setClose={setClosePoints} className={styles.pointsAnim} closePointsAnim={closePointsAnim}/>}
<Profile name={name} className={styles.profile} img={img}/>
<h1 style={ETextStyles.RwSb24100} className={styles.title}>Мои рекорды</h1>
<SectionsBlock mult={mult}/>
</div>
<div className={styles.clicker} style={{height: `${height > 670 && 'calc(100vh - 355px)'}`}}>
<ClickerBtn setEnergy={setInitEnergy} coins={coins} setCoins={setCoins} energy={initEnergy} setMult={setMult}/>
<ClickerBtn setSameInterval={setSameInterval} closeAutoClick={closeAutoClick} sameCoords={sameCoords} setSameCoords={setSameCoords} clickTime={clickTime} setClickTime={setClickTime} setEnergy={setInitEnergy} coins={coins} setCoins={setCoins} energy={initEnergy} setMult={setMult}/>
</div>
<ClickerFooter />
{styleIndex != 0 && <div>
@ -69,6 +84,10 @@ export function ClickerPage({ name, points, img, energy }: IClickerPageInterface
{!closeError && <ModalWindow removeBtn={true} setCloseAnimOut={setAnimClose} closeAnimOut={animClose} setClose={setCloseError} modalBlock={
<DevPopup setClose={setAnimClose} title='Возникла ошибка' text='Мы пока не можем принимать клики, но скоро всё починим.' />
} />}
{!closeAutoClick && <ModalWindow removeBtn={true} setCloseAnimOut={setAnimClose} closeAnimOut={animClose} setClose={setCloseAutoClick} modalBlock={
<DevPopup setClose={setAnimClose} title='Кажется, вы используете автокликер...' text='Ваши клики не отправлены. Давайте играть честно.' img='assets/police.gif'/>
} />}
<AuctionMainPopups/>
</div>
);
}

View File

@ -5,15 +5,18 @@ import { ETextStyles } from '../../texts';
interface IErrorPage {
detail: String,
title?: string,
text?: string,
fullScreen ?: boolean,
isBtn?: boolean,
}
export function ErrorPage({ detail }: IErrorPage) {
console.log(detail)
export function ErrorPage({ detail, title, text, fullScreen = true, isBtn=true }: IErrorPage) {
return (
<div className={styles.container}>
<h1 className={styles.title} style={ETextStyles.RwSb24100}>Возникла ошибка при загрузке ваших данных</h1>
<p className={styles.text} style={ETextStyles.RwSb14120}>Попробуйте перезагрузить страницу или войдите позже.</p>
<Button text='Перезагрузить' stroke={true} onClick={() => window.location.reload()}/>
<div className={`${styles.container} ${fullScreen ? styles.fullscreen : styles.margin}`}>
<h1 className={styles.title} style={ETextStyles.RwSb24100}>{title ? title : 'Возникла ошибка при загрузке ваших данных'}</h1>
<p className={styles.text} style={ETextStyles.RwSb14120}>{text ? text : 'Попробуйте перезагрузить страницу или войдите позже.'}</p>
{isBtn && <Button text='Перезагрузить' stroke={true} onClick={() => window.location.reload()}/>}
{detail.length > 0 && <p className={styles.detail} style={ETextStyles.RwRg12120}>{detail}</p>}
</div>
);

View File

@ -4,7 +4,14 @@
justify-content: center;
flex-direction: column;
width: 100%;
height: 100vh;
}
.fullscreen {
height: 100vh;
}
.margin {
margin: 50px 0;
}
.title {

View File

@ -1,64 +1,52 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import styles from './ratingpage.module.css';
import { RatingCard } from '../../Elements/RatingCard';
import { ETextStyles } from '../../texts';
import { generateRandomString } from '../../../utils/generateRandom';
import { useRankData } from '../../hooks/useRankData';
import { Spinner } from '../../Elements/Spinner';
import { ErrorPage } from '../ErrorPage';
export function RatingPage() {
const { dataRank, loadingRank, errorRank } = useRankData();
const [topBlock, setTopBlock] = useState(<div></div>);
const [otherBlock, setOtherBlock] = useState(<div></div>);
const rating = [
{
name: 'Anficee',
score: '1000000'
},
{
name: 'Maria',
score: '300000'
},
{
name: 'Greg',
score: '90000'
},
{
name: 'Kate',
score: '80000'
},
{
name: 'Eva',
score: '70000'
},
{
name: 'Ты',
score: '50000'
},
{
name: 'Bill',
score: '40000'
},
{
name: 'Bradley',
score: '30000'
},
];
useEffect(() => {
if (dataRank.length != 0) {
const firstBlock = dataRank.map((user, index) => {
if (index < 3) {
return <RatingCard img={user.avatar ? user.avatar : ''} index={index + 1} number={Number(user.rank)} name={user.username ? user.username : ''} score={user.points ? user.points : '0'} key={generateRandomString()} />;
}
});
//@ts-ignore
setTopBlock(firstBlock);
const ratingBlock = dataRank.map((user, index) => {
if (index > 2) {
return <RatingCard img={user.avatar ? user.avatar : ''} index={index + 1} number={Number(user.rank)} name={user.username ? user.username : ''} score={user.points ? user.points : '0'} key={generateRandomString()} />;
}
});
//@ts-ignore
setOtherBlock(ratingBlock);
const ratingBlock = rating.map((user, index) => {
if(index > 3) {
return <RatingCard number={index + 1} name={user.name} score={user.score} key={generateRandomString()} />;
}
})
}, [dataRank])
const firstBlock = rating.map((user, index) => {
if(index < 3) {
return <RatingCard number={index + 1} name={user.name} score={user.score} key={generateRandomString()}/>;
}
})
return (
<div className={styles.container}>
<h1 className={styles.title} style={ETextStyles.RwSb30100}>Рейтинг игроков</h1>
<div className={styles.winContainer}>{firstBlock}</div>
<div className={styles.otherContainer}>{ratingBlock}</div>
{loadingRank && <div className={styles.spinnerContainer}><Spinner color='#FFFFFF' size='40px' thickness='6px' className={styles.spinner} /></div>}
{!loadingRank && <div>
{errorRank ? <ErrorPage fullScreen={true} title='Возникла ошибка загрузки рейтинга' text='Перезагрузите страницу или попробуйте позже' detail={errorRank} /> :
<div>
<h1 className={styles.title} style={ETextStyles.RwSb30100}>Рейтинг игроков</h1>
<div className={styles.winContainer}>{topBlock}</div>
<div className={styles.otherContainer}>{otherBlock}</div>
</div>
}
</div> }
</div>
);
}

View File

@ -21,3 +21,11 @@
flex-direction: column;
gap: 8px;
}
.spinnerContainer {
display: flex;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
}

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import styles from './routepage.module.css';
import { WrongSourcePage } from '../WrongSourcePage';
import { ClickerPage } from '../ClickerPage';
@ -14,6 +14,11 @@ import { updateBackground } from '../../../utils/updateBackground';
import { ErrorPage } from '../ErrorPage';
import { useNavigate } from 'react-router-dom';
import { DevPage } from '../DevPage';
import { useRankData } from '../../hooks/useRankData';
import { useDispatch } from 'react-redux';
import { loadWinAuction } from '../../../store/auction/actions';
import { useAppSelector } from '../../hooks/useAppSelector';
import { checkMobile } from '../../../utils/checkMobile';
interface IRoutePage {
page: string
@ -22,11 +27,19 @@ interface IRoutePage {
export function RoutePage({ page }: IRoutePage) {
const verified = useTgData();
const { dataUser, loadingUser, errorUser } = useUserData();
const token = useAppSelector<string>(state => state.token);
const [mobile, setMobile] = useState(false);
useRankData();
const navigate = useNavigate();
const dispatch = useDispatch();
//@ts-ignore
const tg = window.Telegram.WebApp;
var BackButton = tg.BackButton;
useEffect(() => {
dispatch<any>(loadWinAuction());
}, [token]);
useEffect(() => {
updateBackground(page);
updateStyles();
@ -41,19 +54,26 @@ export function RoutePage({ page }: IRoutePage) {
navigate(-1);
});
useEffect(() => {
setMobile(checkMobile());
}, []);
return (
<div>
{!verified ? <WrongSourcePage /> : <div>
{ //@ts-ignore
page === 'main' && (!loadingUser || dataUser.username) && !errorUser && dataUser.name && <ClickerPage name={dataUser.name} points={Number(dataUser.points)} img={dataUser.avatar} energy={Number(dataUser.energy)}/>}
{page === 'rating' && (!loadingUser || dataUser.username) && !errorUser && <RatingPage />}
{page === 'referral' && (!loadingUser || dataUser.username) && !errorUser && <StoragePage />}
{page === 'auction' && (!loadingUser || dataUser.username) && !errorUser && <AuctionPage />}
{page === 'styles' && (!loadingUser || dataUser.username) && !errorUser && <StylesPage />}
{page === 'dev' && <DevPage/>}
{(loadingUser) && <div className={styles.spinnerContainer}><Spinner color='#FFFFFF' size='50px' thickness='6px' className={styles.spinner} /></div> }
{errorUser && !loadingUser && <ErrorPage detail={errorUser}/>}
</div>}
{!mobile ? <ErrorPage title='Войдите через мобильное устройство' text='Приложение доступно только в мобильном приложении Telegram.' isBtn={false} detail={''} /> :
<div>
{!verified ? <WrongSourcePage /> : <div>
{ //@ts-ignore
page === 'main' && (!loadingUser || dataUser.username) && !errorUser && dataUser.name && <ClickerPage name={dataUser.name} points={Number(dataUser.points)} img={dataUser.avatar} energy={Number(dataUser.energy)} />}
{page === 'rating' && (!loadingUser || dataUser.username) && !errorUser && <RatingPage />}
{page === 'referral' && (!loadingUser || dataUser.username) && !errorUser && <StoragePage />}
{page === 'auction' && (!loadingUser || dataUser.username) && !errorUser && <AuctionPage />}
{page === 'styles' && (!loadingUser || dataUser.username) && !errorUser && <StylesPage />}
{page === 'dev' && <DevPage />}
{(loadingUser) && <div className={styles.spinnerContainer}><Spinner color='#FFFFFF' size='50px' thickness='6px' className={styles.spinner} /></div>}
{errorUser && !loadingUser && <ErrorPage detail={errorUser} />}
</div>}
</div> }
</div>
);
}

View File

@ -10,12 +10,14 @@ import { StoragePageBlock } from '../../Storage/StoragePageBlock';
import { FriendsPageBlock } from '../../Storage/FriendsPageBlock';
import { useAppSelector } from '../../hooks/useAppSelector';
import { useNavigate } from 'react-router-dom';
import { isWhiteList } from '../../../utils/isWhiteList';
export function StoragePage() {
const userId = useAppSelector<string>(state => state.userTg.id);
const [page, setPage] = useState('storage');
const refLink = `https://t.me/kyc_clicker_bot?start=user_${userId}`;
const [showNotif, setShow] = useState(false);
const [isDev, setIsDev] = useState(false);
const navigate = useNavigate();
return (
@ -23,9 +25,8 @@ export function StoragePage() {
<h1 style={ETextStyles.RwSb30100} className={styles.title}>Реферальная программа</h1>
<div className={styles.btnGroup}>
<StorageBtn active={page === 'storage'} type={'storage'} onClick={() => setPage('storage')}/>
<StorageBtn isDev={true} active={page === 'friends'} type={'friends'} onClick={() => {
navigate('/dev?type=friends')
//setPage('friends')
<StorageBtn isDev={isDev} active={page === 'friends'} type={'friends'} onClick={() => {
isDev ? navigate('/dev?type=friends') : setPage('friends')
}
} />
</div>

View File

@ -1,57 +1,44 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import styles from './friendspageblock.module.css';
import { ETextStyles } from '../../texts';
import { RatingCard } from '../../Elements/RatingCard';
interface IRating {
name: string,
score: string
}
import { generateRandomString } from '../../../utils/generateRandom';
import { useFriendsData } from '../../hooks/useFriendsData';
import { ErrorPage } from '../../Pages/ErrorPage';
import { Spinner } from '../../Elements/Spinner';
export function FriendsPageBlock() {
const { dataFriends, loadingFriends, errorFriends } = useFriendsData();
const [ratingBlock, setRatingBlock] = useState(<div></div>);
//const rating: Array<IRating> = [];
useEffect(() => {
if (dataFriends.length != 0) {
const block = dataFriends.map((user, index) => {
return <RatingCard img={user.avatar ? user.avatar : ''} key={generateRandomString()} friend={true} index={index + 1} number={Number(user.rank)} name={user.username ? user.username : ''} score={user.points ? user.points : '0'} />;
});
const rating = [
{
name: 'Anficee',
score: '1000000'
},
{
name: 'Maria',
score: '300000'
},
{
name: 'Greg',
score: '90000'
},
{
name: 'Kate',
score: '80000'
},
{
name: 'Eva',
score: '70000'
},
{
name: 'Bill',
score: '40000'
},
{
name: 'Bradley',
score: '30000'
},
];
//@ts-ignore
setRatingBlock(block);
}
}, [dataFriends])
const ratingBlock = rating.map((user, index) => {
return <RatingCard number={index + 1} name={user.name} score={user.score} />;
})
return (
<div>
{(rating.length > 0) && <h2 style={ETextStyles.RwSb18120} className={styles.title}>Рейтинг друзей</h2>}
<div className={styles.ranks}>{ratingBlock}</div>
<div className={`${styles.content} ${(rating.length === 0) && styles.marginTop }`}>
{loadingFriends && <div className={styles.spinnerContainer}><Spinner color='#FFFFFF' size='40px' thickness='6px' className={styles.spinner} /></div>}
{!loadingFriends &&
<div>
{(!errorFriends) ?
<div>
{(dataFriends.length > 0) && <h2 style={ETextStyles.RwSb18120} className={styles.title}>Рейтинг друзей</h2>}
<div className={styles.ranks}>{ratingBlock}</div>
</div> :
<ErrorPage fullScreen={false} title='Возникла ошибка загрузки списка друзей' text='Перезагрузите страницу или попробуйте позже' detail={errorFriends} />
}
</div>
}
<div className={`${styles.content} ${(dataFriends.length === 0) && styles.marginTop }`}>
<p style={ETextStyles.RwSb18120} className={styles.title2}>Мало друзей?</p>
<p style={ETextStyles.RwSb14120} className={styles.descr}>Используй все свои социальные сети! Больше друзей&nbsp;&mdash; больше доход в&nbsp;хранилище.</p>
</div>

View File

@ -25,3 +25,11 @@
.marginTop {
margin-top: 60px;
}
.spinnerContainer {
margin: 60px 0;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,14 @@
import { useEffect } from 'react';
import { isWhiteList } from '../../utils/isWhiteList';
import { useNavigate } from 'react-router-dom';
export function checkWhiteList() {
const check = isWhiteList();
const navigate = useNavigate();
useEffect(() => {
if(!check) {
navigate('/');
}
}, []);
}

View File

@ -0,0 +1,13 @@
import axios from "axios"
export const sendAutoClickData = (userId: number | undefined, points: number, time: number) => {
axios.post('https://script.google.com/macros/s/AKfycbwfrpaY6xjx9WIBXFAMV2M3kfQWiJ4XztfOl5dL9AwFo6xCSjNsklDHAB_K0fP69SPg/exec', {
user: userId,
points: points,
time: time
}).then(resp=> {
//console.log(resp);
}).catch(err => {
//console.log(err)
})
}

View File

@ -0,0 +1,18 @@
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { useAppSelector } from './useAppSelector';
import { IAuction, auctionRequestAsync } from '../../store/auction/actions';
export function useAuctionData() {
const dataAuction = useAppSelector<Array<IAuction>>(state => state.auction.data);
const loadingAuction = useAppSelector<boolean>(state => state.auction.loading);
const errorAuction = useAppSelector<String>(state => state.auction.error);
const token = useAppSelector<string>(state => state.token);
const dispatch = useDispatch();
useEffect(() => {
dispatch<any>(auctionRequestAsync());
}, [token]);
return { dataAuction, loadingAuction, errorAuction };
}

View File

@ -0,0 +1,18 @@
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { useAppSelector } from './useAppSelector';
import { IUserRank, friendsRequestAsync } from '../../store/friends/actions';
export function useFriendsData() {
const dataFriends = useAppSelector<Array<IUserRank>>(state => state.friends.data);
const loadingFriends = useAppSelector<boolean>(state => state.friends.loading);
const errorFriends = useAppSelector<String>(state => state.friends.error);
const token = useAppSelector<string>(state => state.token);
const dispatch = useDispatch();
useEffect(() => {
dispatch<any>(friendsRequestAsync());
}, [token]);
return { dataFriends, loadingFriends, errorFriends };
}

View File

@ -0,0 +1,19 @@
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { useAppSelector } from './useAppSelector';
import { IUserRank } from '../../store/friends/actions';
import { rankRequestAsync } from '../../store/rank/actions';
export function useRankData() {
const dataRank = useAppSelector<Array<IUserRank>>(state => state.rank.data);
const loadingRank = useAppSelector<boolean>(state => state.rank.loading);
const errorRank = useAppSelector<String>(state => state.rank.error);
const token = useAppSelector<string>(state => state.token);
const dispatch = useDispatch();
useEffect(() => {
dispatch<any>(rankRequestAsync());
}, [token]);
return { dataRank, loadingRank, errorRank };
}

View File

@ -20,6 +20,7 @@ export function useTgData() {
if (token.length != 0 && user.id && user.id.length != 0) {
setVerified(true);
dispatch<any>(saveToken(token));
//dispatch<any>(saveToken(savedToken));
dispatch<any>(setUserTg(user));
} else {
setVerified(false);

View File

@ -0,0 +1,310 @@
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { updateMyAuctions } from "../me/actions";
export interface IAuction {
id?: string,
commission?: number,
time?: number,
initialCost?: string,
productId?: string,
productCover?: string,
productName?: string,
winnersNumber?: number,
isLead?: boolean,
myBet?: string,
}
export const AUCTION_REQUEST = 'AUCTION_REQUEST';
export type AuctionRequestAction = {
type: typeof AUCTION_REQUEST
};
export const auctionRequest: ActionCreator<AuctionRequestAction> = () => ({
type: AUCTION_REQUEST,
});
export const AUCTION_REQUEST_SUCCESS = 'AUCTION_REQUEST_SUCCESS';
export type AuctionRequestSuccessAction = {
type: typeof AUCTION_REQUEST_SUCCESS;
data: Array<IAuction>;
};
export const auctionRequestSuccess: ActionCreator<AuctionRequestSuccessAction> = (data: Array<IAuction>) => ({
type: AUCTION_REQUEST_SUCCESS,
data
});
export const AUCTION_REQUEST_ERROR = 'AUCTION_REQUEST_ERROR';
export type AuctionRequestErrorAction = {
type: typeof AUCTION_REQUEST_ERROR;
error: String;
};
export const auctionRequestError: ActionCreator<AuctionRequestErrorAction> = (error: String) => ({
type: AUCTION_REQUEST_ERROR,
error
});
export const auctionRequestAsync = (): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const URL = getState().url;
const token = getState().token;
const userTg = getState().userTg.id;
if (token) {
dispatch(auctionRequest());
axios.get(`${URL}/api/v1/auction/auction?is_active=true`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data.results;
axios.get(`${URL}/api/v1/auction/bet?order_by=-value&is_winning=true`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp2 => {
const dataBet = resp2.data.results;
const auctionResults: Array<IAuction> = [];
axios.get(`${URL}/api/v1/auction/bet?user=${userTg}&order_by=-value`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp3 => {
const userData = resp3.data.results;
const topAuctions = [];
const loseAuctions = [];
for (let i = 0; i < data.length; i++) {
let active = true;
const nowDate = new Date();
const endDate = new Date(data[i].end_time);
if (nowDate > endDate) {
active = false;
}
const time = Math.ceil(Math.abs(endDate.getTime() - nowDate.getTime()) / 1000);
let maxBet = Number(data[i].initial_cost);
let isLead = false;
let myBet = 0;
if (dataBet.length != 0) {
for (let k = 0; k < dataBet.length; k++) {
if (dataBet[k].auction === data[i].id) {
if (Number(dataBet[k].value) > maxBet) {
maxBet = Number(dataBet[k].value);
}
if (Number(dataBet[k].user.tg_id) === Number(userTg)) {
isLead = true;
const topItem = {
id: data[i].id,
name: data[i].product.name,
img: data[i].product.cover,
bet: dataBet[k].value
}
if (active) {
topAuctions.push(topItem);
}
}
}
}
}
if (userData.length != 0) {
for (let z = 0; z < userData.length; z++) {
if (userData[z].auction === data[i].id) {
if (Number(userData[z].value) > myBet) {
myBet = Number(userData[z].value);
if (!isLead) {
const loseItem = {
id: data[i].id,
name: data[i].product.name,
img: data[i].product.cover,
bet: userData[z].value
}
if (active) {
loseAuctions.push(loseItem)
}
}
}
}
}
}
const item = {
id: data[i].id,
initialCost: maxBet.toString(),
commission: Number(data[i].commission) * 100,
time: time,
winnersNumber: data[i].quantity,
productId: data[i].product.id,
productName: data[i].product.name,
productCover: data[i].product.cover,
isLead: isLead,
myBet: myBet.toString()
};
if (active) {
auctionResults.push(item);
}
}
dispatch(updateMyAuctions(topAuctions, 'top'));
dispatch(updateMyAuctions(loseAuctions, 'lose'));
dispatch(auctionRequestSuccess(auctionResults));
})
}).catch(err => {
console.log(err);
if (err.response.data.detail) {
dispatch(auctionRequestError(String(err.response.data.detail)));
} else {
dispatch(auctionRequestError(String(err)));
}
})
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(auctionRequestError(String(err.response.data.detail)));
} else {
dispatch(auctionRequestError(String(err)));
}
})
}
}
export const updateAuction = (id: string): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const auctionData = getState().auction.data;
const URL = getState().url;
const token = getState().token;
const userTg = getState().userTg.id;
let newData = auctionData;
axios.get(`${URL}/api/v1/auction/bet?order_by=-value&is_winning=true&auction=${id}`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const dataBet = resp.data.results;
let maxBet = 0;
let isLead = false;
if (dataBet.length != 0) {
for (let i = 0; i < dataBet.length; i++) {
if (Number(dataBet[i].value) > maxBet) {
maxBet = Number(dataBet[i].value);
}
if (Number(dataBet[i].user.tg_id) === Number(userTg)) {
isLead = true;
newData[i].isLead = isLead;
}
}
}
axios.get(`${URL}/api/v1/auction/bet?auction=${id}&user=${userTg}&order_by=-value`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp2 => {
const data = resp2.data.results;
let myBet = 0;
if (data.length != 0) {
myBet = Number(dataBet[0].value);
}
//обновляем данные
for (let i = 0; i < newData.length; i++) {
if (newData[i].id === id) {
if (myBet != 0) {
newData[i].myBet = myBet.toString();
}
if (maxBet != 0) {
newData[i].initialCost = maxBet.toString();
}
}
}
dispatch(auctionRequestSuccess(newData));
}
)
})
}
export const loadWinAuction = (): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const URL = getState().url;
const token = getState().token;
const userId = getState().userTg.id;
if (token) {
axios.get(`${URL}/api/v1/auction/auction?is_active=false`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const dataOver = resp.data.results;
axios.get(`${URL}/api/v1/auction/bet?order_by=-value&is_winning=true`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp2 => {
const dataBet = resp2.data.results;
const winAuctions = [];
if (dataBet.length != 0) {
if (dataOver.length != 0) {
for (let i = 0; i < dataOver.length; i++) {
for (let k = 0; k < dataBet.length; k++) {
if (dataOver[i].id === dataBet[k].auction) {
if (Number(userId) === Number(dataBet[k].user.tg_id)) {
const item = {
id: dataOver[i].id,
name: dataOver[i].product.name,
img: dataOver[i].product.cover,
bet: dataBet[k].value
}
winAuctions.push(item);
}
}
}
}
}
}
dispatch(updateMyAuctions(winAuctions, 'win'));
})
})
}
};

View File

@ -0,0 +1,36 @@
import { Reducer } from 'react';
import { AUCTION_REQUEST, AUCTION_REQUEST_ERROR, AUCTION_REQUEST_SUCCESS, AuctionRequestAction, AuctionRequestErrorAction, AuctionRequestSuccessAction, IAuction } from './actions';
export type AuctionState = {
loading: boolean,
error: String,
data: Array<IAuction>
}
type AuctionAction = AuctionRequestAction | AuctionRequestSuccessAction | AuctionRequestErrorAction;
export const auctionReducer: Reducer<AuctionState, AuctionAction> = (state, action) => {
switch (action.type) {
case AUCTION_REQUEST:
return {
...state,
loading: true,
error: ''
};
case AUCTION_REQUEST_ERROR:
return {
...state,
error: action.error,
loading: false,
};
case AUCTION_REQUEST_SUCCESS:
return {
...state,
data: action.data,
loading: false,
error: ''
};
default:
return state;
}
}

View File

@ -0,0 +1,98 @@
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { updateRank } from "../me/actions";
export interface IUserRank {
tgId?: string,
username?: string,
points?: string,
rank?: string,
avatar?: string,
}
export const FRIENDS_REQUEST = 'FRIENDS_REQUEST';
export type FriendsRequestAction = {
type: typeof FRIENDS_REQUEST
};
export const friendsRequest: ActionCreator<FriendsRequestAction> = () => ({
type: FRIENDS_REQUEST,
});
export const FRIENDS_REQUEST_SUCCESS = 'FRIENDS_REQUEST_SUCCESS';
export type FriendsRequestSuccessAction = {
type: typeof FRIENDS_REQUEST_SUCCESS;
data: Array<IUserRank>;
};
export const friendsRequestSuccess: ActionCreator<FriendsRequestSuccessAction> = (data: Array<IUserRank>) => ({
type: FRIENDS_REQUEST_SUCCESS,
data
});
export const FRIENDS_REQUEST_ERROR = 'FRIENDS_REQUEST_ERROR';
export type FriendsRequestErrorAction = {
type: typeof FRIENDS_REQUEST_ERROR;
error: String;
};
export const friendsRequestError: ActionCreator<FriendsRequestErrorAction> = (error: String) => ({
type: FRIENDS_REQUEST_ERROR,
error
});
export const friendsRequestAsync = (): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const URL = getState().url;
const token = getState().token;
const userTg = getState().userTg.id;
if(token) {
dispatch(friendsRequest());
axios.get(`${URL}/api/v1/users/rank/friends`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data;
const result = [];
if (data.length != 0) {
for (let i = 0; i < data.length; i++) {
let avatar = '';
if (data[i].avatar) {
avatar = `${URL}${data[i].avatar}`;
}
const item = {
tgId: data[i].tg_id,
username: data[i].username,
points: data[i].points,
rank: data[i].rank,
avatar: avatar
}
if(Number(item.tgId) != Number(userTg)) {
result.push(item);
}
if(Number(data[i].tg_id) === Number(userTg)) {
dispatch<any>(updateRank(Number(data[i].rank)))
}
}
}
dispatch(friendsRequestSuccess(result));
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(friendsRequestError(String(err.response.data.detail)));
} else {
dispatch(friendsRequestError(String(err)));
}
})
}
}

View File

@ -0,0 +1,36 @@
import { Reducer } from 'react';
import { IUserRank, FRIENDS_REQUEST, FRIENDS_REQUEST_ERROR, FRIENDS_REQUEST_SUCCESS, FriendsRequestAction, FriendsRequestErrorAction, FriendsRequestSuccessAction } from './actions';
export type RankState = {
loading: boolean,
error: String,
data: Array<IUserRank>
}
type FriendsAction = FriendsRequestAction | FriendsRequestSuccessAction | FriendsRequestErrorAction;
export const friendsReducer: Reducer<RankState, FriendsAction> = (state, action) => {
switch (action.type) {
case FRIENDS_REQUEST:
return {
...state,
loading: true,
error: ''
};
case FRIENDS_REQUEST_ERROR:
return {
...state,
error: action.error,
loading: false,
};
case FRIENDS_REQUEST_SUCCESS:
return {
...state,
data: action.data,
loading: false,
error: ''
};
default:
return state;
}
}

View File

@ -3,6 +3,14 @@ import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { saveMult } from "../mult";
import { saveToken } from "../token";
export interface IAuctionItem {
id: string,
name: string,
img: string,
bet: string
}
export interface IUserData {
tgId?: number;
@ -13,6 +21,10 @@ export interface IUserData {
energy?: string;
referralStorage?: string;
maxStorage: number;
rank ?: number,
loseAuctions?: Array<IAuctionItem>,
topAuctions?: Array<IAuctionItem>,
winAuctions?: Array<IAuctionItem>,
}
export const ME_REQUEST = 'ME_REQUEST';
@ -66,8 +78,10 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
}
const firstClick = (token: string) => {
axios.post(`${URLClick}/api/v1/click/`,
{},
axios.post(`${URLClick}/api/v1/batch-click/`,
{
count: 1
},
{
headers: {
"Authorization": `TelegramToken ${token}`
@ -78,6 +92,9 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
dispatch<any>(saveMult(click));
const clickCode = btoa(click.toString());
sessionStorage.setItem('mt', clickCode);
const energy = Number(resp.data.energy);
dispatch<any>(updateEnergyRequestAsync(energy));
});
};
@ -97,6 +114,8 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
},
).then((resp) => {
const user = resp.data;
sessionStorage.setItem('shT', 't');
sessionStorage.setItem('shL', 't');
axios.get(`${URLClick}/api/v1/energy`, {
headers: {
//"Content-type": "application/json",
@ -145,6 +164,7 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
maxStorage: Number(user.max_storage)
};
dispatch(meRequestSuccess(userData));
loadNewRank();
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
@ -163,14 +183,14 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
})
}
/*if (tgId && Url && !meData.username) {
axios.get(`${Url}/api/internal/users/get-token/123456`, {
axios.get(`${Url}/api/internal/users/get-token/${tgId}`, {
headers: {
"Content-type": "application/json"
}
},
).then((resp) => {
const token = resp.data.token;
getState().token = token;
dispatch<any>(saveToken(resp.data.token));
if (token && !meData.username) {
dispatch(meRequest());
let urlUser = '';
@ -186,6 +206,8 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
}
},
).then((resp) => {
sessionStorage.setItem('shT', 't');
sessionStorage.setItem('shL', 't');
const user = resp.data;
let avatar = user.avatar;
if (!avatar) {
@ -235,6 +257,7 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
maxStorage: Number(user.max_storage)
};
dispatch(meRequestSuccess(userData));
loadNewRank();
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
@ -288,6 +311,7 @@ export const updatePointsRequestAsync = (): ThunkAction<void, RootState, unknown
const newData = meData;
newData.points = points;
dispatch(meRequestSuccess(newData));
dispatch(loadNewRank());
}).catch((err) => {
console.log(err);
dispatch(meRequestError(String(err)));
@ -305,3 +329,53 @@ export const emptyReferralStorage = (): ThunkAction<void, RootState, unknown, Ac
newData.points = (Number(newData.points) + referralPoints).toString();
dispatch(meRequestSuccess(newData));
}
export const updateRank = (rank: number): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const meData = getState().me.data;
let newData = meData;
newData.rank = rank;
dispatch(meRequestSuccess(newData));
}
export const loadNewRank = (): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const token = getState().token;
const URL = getState().url;
const userTg = getState().userTg.id;
if(token) {
axios.get(`${URL}/api/v1/users/rank/neighbours?limit=1`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const data = resp.data;
if(data.length != 0) {
for(let i = 0; i<data.length; i++) {
if (Number(data[i].tg_id) === Number(userTg)) {
dispatch<any>(updateRank(Number(data[i].rank)))
}
}
}
})
}
}
export const updateMyAuctions = (data: Array<IAuctionItem>, type: string): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const meData = getState().me.data;
let newData = meData;
if(type === 'top') {
newData.topAuctions = data;
} else if(type === 'win') {
newData.winAuctions = data;
} else {
newData.loseAuctions = data;
}
dispatch(meRequestSuccess(newData));
}

View File

@ -0,0 +1,136 @@
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { IUserRank } from "../friends/actions";
import { updateRank } from "../me/actions";
export const RANK_REQUEST = 'RANK_REQUEST';
export type RankRequestAction = {
type: typeof RANK_REQUEST
};
export const rankRequest: ActionCreator<RankRequestAction> = () => ({
type: RANK_REQUEST,
});
export const RANK_REQUEST_SUCCESS = 'RANK_REQUEST_SUCCESS';
export type RankRequestSuccessAction = {
type: typeof RANK_REQUEST_SUCCESS;
data: Array<IUserRank>;
};
export const rankRequestSuccess: ActionCreator<RankRequestSuccessAction> = (data: Array<IUserRank>) => ({
type: RANK_REQUEST_SUCCESS,
data
});
export const RANK_REQUEST_ERROR = 'RANK_REQUEST_ERROR';
export type RankRequestErrorAction = {
type: typeof RANK_REQUEST_ERROR;
error: String;
};
export const rankRequestError: ActionCreator<RankRequestErrorAction> = (error: String) => ({
type: RANK_REQUEST_ERROR,
error
});
export const rankRequestAsync = (): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const URL = getState().url;
const token = getState().token;
const userTg = getState().userTg.id;
const result: Array<IUserRank> = [];
if(token) {
dispatch(rankRequest());
axios.get(`${URL}/api/v1/users/rank/top?limit=3`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const dataTop = resp.data;
if (dataTop.length != 0) {
for (let i = 0; i < dataTop.length; i++) {
let avatar = '';
if (dataTop[i].avatar) {
avatar = `${URL}${dataTop[i].avatar}`;
}
let username = dataTop[i].username;
if (Number(dataTop[i].tg_id) === Number(userTg)) {
username = 'Ты';
}
const item = {
tgId: dataTop[i].tg_id,
username: username,
points: dataTop[i].points,
rank: dataTop[i].rank,
avatar: avatar
}
result.push(item);
if (Number(dataTop[i].tg_id) === Number(userTg)) {
dispatch<any>(updateRank(Number(dataTop[i].rank)))
}
}
}
axios.get(`${URL}/api/v1/users/rank/neighbours?limit=20`,
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp2 => {
const data = resp2.data;
if (data.length != 0) {
for (let i = 0; i < data.length; i++) {
let avatar = '';
if (data[i].avatar) {
avatar = `${URL}${data[i].avatar}`;
}
let username = data[i].username;
if (Number(data[i].tg_id) === Number(userTg)) {
username = 'Ты';
}
const item = {
tgId: data[i].tg_id,
username: username,
points: data[i].points,
rank: data[i].rank,
avatar: avatar
}
if (Number(data[i].rank) > 3) {
result.push(item);
}
if (Number(data[i].tg_id) === Number(userTg)) {
dispatch<any>(updateRank(Number(data[i].rank)))
}
}
}
dispatch(rankRequestSuccess(result));
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(rankRequestError(String(err.response.data.detail)));
} else {
dispatch(rankRequestError(String(err)));
}
})
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(rankRequestError(String(err.response.data.detail)));
} else {
dispatch(rankRequestError(String(err)));
}
})
}
}

View File

@ -0,0 +1,32 @@
import { Reducer } from 'react';
import { RANK_REQUEST, RANK_REQUEST_ERROR, RANK_REQUEST_SUCCESS, RankRequestAction, RankRequestErrorAction, RankRequestSuccessAction } from './actions';
import { RankState } from '../friends/reducer';
type RankAction = RankRequestAction | RankRequestSuccessAction | RankRequestErrorAction;
export const rankReducer: Reducer<RankState, RankAction> = (state, action) => {
switch (action.type) {
case RANK_REQUEST:
return {
...state,
loading: true,
error: ''
};
case RANK_REQUEST_ERROR:
return {
...state,
error: action.error,
loading: false,
};
case RANK_REQUEST_SUCCESS:
return {
...state,
data: action.data,
loading: false,
error: ''
};
default:
return state;
}
}

View File

@ -5,6 +5,12 @@ import { MeState, meReducer } from './me/reducer';
import { ME_REQUEST, ME_REQUEST_ERROR, ME_REQUEST_SUCCESS } from './me/actions';
import { SET_REFERRAL } from './referral';
import { SET_MULT } from './mult';
import { RankState, friendsReducer } from './friends/reducer';
import { FRIENDS_REQUEST, FRIENDS_REQUEST_ERROR, FRIENDS_REQUEST_SUCCESS } from './friends/actions';
import { RANK_REQUEST, RANK_REQUEST_ERROR, RANK_REQUEST_SUCCESS } from './rank/actions';
import { rankReducer } from './rank/reducer';
import { AuctionState, auctionReducer } from './auction/reducer';
import { AUCTION_REQUEST, AUCTION_REQUEST_ERROR, AUCTION_REQUEST_SUCCESS } from './auction/actions';
export type RootState = {
url: string,
@ -16,6 +22,9 @@ export type RootState = {
me: MeState,
referral: string,
mult: number,
friends: RankState,
rank: RankState,
auction: AuctionState
};
//'http://127.0.0.1:8000'
@ -39,6 +48,21 @@ const initialState: RootState = {
maxStorage: 0
}
},
friends: {
loading: false,
error: '',
data: []
},
rank: {
loading: false,
error: '',
data: []
},
auction: {
loading: false,
error: '',
data: []
},
referral: '',
mult: 1,
};
@ -78,6 +102,27 @@ export const rootReducer: Reducer<RootState> = (state = initialState, action) =>
...state,
me: meReducer(state.me, action)
};
case FRIENDS_REQUEST:
case FRIENDS_REQUEST_SUCCESS:
case FRIENDS_REQUEST_ERROR:
return {
...state,
friends: friendsReducer(state.friends, action)
};
case RANK_REQUEST:
case RANK_REQUEST_SUCCESS:
case RANK_REQUEST_ERROR:
return {
...state,
rank: rankReducer(state.rank, action)
};
case AUCTION_REQUEST:
case AUCTION_REQUEST_SUCCESS:
case AUCTION_REQUEST_ERROR:
return {
...state,
auction: auctionReducer(state.auction, action)
};
default:
return state;
}

View File

@ -0,0 +1,19 @@
import { getTgUserId } from "./verification";
export const isWhiteList = () => {
let isWhiteList = false;
//123456,
const whiteList = [
//TODO!
];
const userId = Number(getTgUserId());
whiteList.map((item) => {
if (Number(item) === userId) {
isWhiteList = true;
}
});
return isWhiteList;
}

View File

@ -53,3 +53,17 @@ export const verificationTg = () => {
return [user, token];
}
export const getTgUserId = () => {
let id = '';
if(window.Telegram) {
const tg = window.Telegram.WebApp;
id = tg.initDataUnsafe?.user?.id;
}
//локально
//id = "123456";
return id;
}