autoclick add & add ranks

This commit is contained in:
Arseniy Sitnikov 2024-12-12 23:01:22 +03:00
parent 9e9fcc5f94
commit 16932959d5
29 changed files with 718 additions and 225 deletions

BIN
frontend/src/.DS_Store vendored

Binary file not shown.

View File

@ -1,27 +1,26 @@
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
}
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, 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 +31,22 @@ 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);
useEffect(() => {
if(!closeAutoClick) {
setClose(true);
}
}, [closeAutoClick]);
useEffect(() => {
if(clickTime === 0) {
setPrevClickTime(0);
setPrevCoords(0);
}
}, [clickTime]);
useEffect(() => {
setFill((maxEnergy - energy) / maxEnergy * 100);
@ -50,7 +65,25 @@ 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 clickInterval = currentTime - prevClickTime;
if(prevClickTime != 0) {
setClickTime(clickTime + clickInterval);
}
setPrevClickTime(currentTime);
if (energy != 0) {
const newEnergy = energy - 1;
const newFill = (maxEnergy - newEnergy) / maxEnergy * 100;
@ -70,70 +103,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 +136,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

@ -4,7 +4,7 @@ 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';
@ -12,56 +12,82 @@ import { saveMult } from '../../../store/mult';
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,
}
export function PointsZoom({ points, setMult, setClose, setCoins, className, closePointsAnim, setClosePointsAnim, setCloseError, setEnergy }: IPointsZoom) {
export function PointsZoom({ points, 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);
if (!node) return null;
const dispatch = useDispatch();
const sendClicks = () => {
const initPoints = points;
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 clickTimeInit = clickTime;
let initSameCoords = sameCoords;
let avtTime = 500;
if (points > 1) {
avtTime = clickTimeInit / (initPoints - 1);
}
//block function
initSameCoords = false;
setClickTime(0);
setSameCoords(false);
if (avtTime < 100 && initSameCoords && points > 30) {
setCloseAutoClick(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(() => {
@ -89,7 +115,7 @@ export function PointsZoom({ points, setMult, setClose, setCoins, className, clo
setSizeHand(30);
}, 100);
return () => clearTimeout(timer);
return () => clearTimeout(timer);
}, [points]);
return (

View File

@ -9,6 +9,8 @@ import { useNavigate } from 'react-router-dom';
import { UsersIcons } from '../../Elements/UsersIcons';
import { formatNumber } from '../../../utils/formatNumber';
import { useAppSelector } from '../../hooks/useAppSelector';
import { isWhiteList } from '../../../utils/isWhiteList';
import { IUserRank } from '../../../store/friends/actions';
interface ISectionsBlock {
mult:number;
@ -18,9 +20,30 @@ 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(true);
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 whiteList = isWhiteList();
setIsDev(!whiteList)
}, []);
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 +54,6 @@ export function SectionsBlock({ mult }: ISectionsBlock) {
}, [referralStorage, maxReferralStorage]);
const isDev = true;
const multipCards = [
{
title: 'Что он делает',
@ -54,13 +75,13 @@ export function SectionsBlock({ mult }: ISectionsBlock) {
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}/>
<UsersIcons imgs={topImgs} 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

@ -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}/>
<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

@ -11,9 +11,9 @@ 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} />
<PersonIcon className={`${styles.userIcon} ${styles.userIcon1}`} size={size} img={imgs[0] ? imgs[0] : ''}/>
<PersonIcon className={`${styles.userIcon} ${styles.userIcon2}`} size={size} left={size / 1.4} img={imgs[1] ? imgs[1] : ''} />
<PersonIcon className={`${styles.userIcon} ${styles.userIcon3}`} size={size} left={2 * size / 1.4} img={imgs[2] ? imgs[2] : ''} />
</div>
);
}

View File

@ -30,6 +30,9 @@ 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);
useEffect(() => {
setMult(savedMult);
@ -54,13 +57,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 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 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 +72,9 @@ 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'/>
} />}
</div>
);
}

View File

@ -5,14 +5,16 @@ import { ETextStyles } from '../../texts';
interface IErrorPage {
detail: String,
title?: string,
text?: string,
fullScreen ?: boolean
}
export function ErrorPage({ detail }: IErrorPage) {
console.log(detail)
export function ErrorPage({ detail, title, text, fullScreen=true }: IErrorPage) {
return (
<div className={styles.container}>
<h1 className={styles.title} style={ETextStyles.RwSb24100}>Возникла ошибка при загрузке ваших данных</h1>
<p className={styles.text} style={ETextStyles.RwSb14120}>Попробуйте перезагрузить страницу или войдите позже.</p>
<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>
<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,54 @@
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';
import { checkWhiteList } from '../../hooks/checkWhiteList';
export function RatingPage() {
const { dataRank, loadingRank, errorRank } = useRankData();
const [topBlock, setTopBlock] = useState(<div></div>);
const [otherBlock, setOtherBlock] = useState(<div></div>);
checkWhiteList();
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

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

View File

@ -14,6 +14,7 @@ import { updateBackground } from '../../../utils/updateBackground';
import { ErrorPage } from '../ErrorPage';
import { useNavigate } from 'react-router-dom';
import { DevPage } from '../DevPage';
import { useRankData } from '../../hooks/useRankData';
interface IRoutePage {
page: string
@ -22,6 +23,7 @@ interface IRoutePage {
export function RoutePage({ page }: IRoutePage) {
const verified = useTgData();
const { dataUser, loadingUser, errorUser } = useUserData();
useRankData();
const navigate = useNavigate();
//@ts-ignore
const tg = window.Telegram.WebApp;

View File

@ -10,22 +10,28 @@ 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/sapphirecrown_bot?start=user_${userId}`;
const [showNotif, setShow] = useState(false);
const [isDev, setIsDev] = useState(true);
const navigate = useNavigate();
useEffect(() => {
const whiteList = isWhiteList();
setIsDev(!whiteList)
}, []);
return (
<div>
<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,7 +1,11 @@
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';
import { generateRandomString } from '../../../utils/generateRandom';
import { useFriendsData } from '../../hooks/useFriendsData';
import { ErrorPage } from '../../Pages/ErrorPage';
import { Spinner } from '../../Elements/Spinner';
interface IRating {
name: string,
@ -9,49 +13,38 @@ interface IRating {
}
export function FriendsPageBlock() {
const { dataFriends, loadingFriends, errorFriends } = useFriendsData();
const [ratingBlock, setRatingBlock] = useState(<div></div>);
const loading = true;
//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'} />;
});
//@ts-ignore
setRatingBlock(block);
}
}, [dataFriends])
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'
},
];
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

@ -24,4 +24,12 @@
.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,24 @@
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { useAppSelector } from './useAppSelector';
import { IUserRank, friendsRequestAsync } from '../../store/friends/actions';
import { isWhiteList } from '../../utils/isWhiteList';
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(() => {
const whiteList = isWhiteList();
if(whiteList) {
dispatch<any>(friendsRequestAsync());
}
}, [token]);
return { dataFriends, loadingFriends, errorFriends };
}

View File

@ -0,0 +1,23 @@
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';
import { isWhiteList } from '../../utils/isWhiteList';
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(() => {
const whiteList = isWhiteList();
if(whiteList) {
dispatch<any>(rankRequestAsync());
}
}, [token]);
return { dataRank, loadingRank, errorRank };
}

View File

@ -19,7 +19,8 @@ export function useTgData() {
const [user, token]: [IUserTg, string] = verificationTg();
if (token.length != 0 && user.id && user.id.length != 0) {
setVerified(true);
dispatch<any>(saveToken(token));
//dispatch<any>(saveToken(token));
dispatch<any>(saveToken(savedToken));
dispatch<any>(setUserTg(user));
} else {
setVerified(false);

View File

@ -0,0 +1,97 @@
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) {
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,7 @@ import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { saveMult } from "../mult";
import { saveToken } from "../token";
export interface IUserData {
tgId?: number;
@ -13,6 +14,7 @@ export interface IUserData {
energy?: string;
referralStorage?: string;
maxStorage: number;
rank ?: number
}
export const ME_REQUEST = 'ME_REQUEST';
@ -66,8 +68,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 +82,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));
});
};
@ -170,7 +177,7 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
},
).then((resp) => {
const token = resp.data.token;
getState().token = token;
dispatch<any>(saveToken(resp.data.token));
if (token && !meData.username) {
dispatch(meRequest());
let urlUser = '';
@ -304,4 +311,12 @@ export const emptyReferralStorage = (): ThunkAction<void, RootState, unknown, Ac
newData.referralStorage = '0';
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));
}

View File

@ -0,0 +1,135 @@
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) {
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,10 @@ 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';
export type RootState = {
url: string,
@ -16,6 +20,8 @@ export type RootState = {
me: MeState,
referral: string,
mult: number,
friends: RankState,
rank: RankState,
};
//'http://127.0.0.1:8000'
@ -39,6 +45,16 @@ const initialState: RootState = {
maxStorage: 0
}
},
friends: {
loading: false,
error: '',
data: []
},
rank: {
loading: false,
error: '',
data: []
},
referral: '',
mult: 1,
};
@ -78,6 +94,20 @@ 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)
};
default:
return state;
}

View File

@ -0,0 +1,20 @@
import { getTgUserId } from "./verification";
export const isWhiteList = () => {
let isWhiteList = false;
//123456,
const whiteList = [
342495217, 6374536117, 322861155, 5219438370, 193428034, 402449803,
406350282, 1083462027,
];
const userId = Number(getTgUserId());
whiteList.map((item) => {
if (Number(item) === userId) {
isWhiteList = true;
}
});
return isWhiteList;
}

View File

@ -52,4 +52,18 @@ export const verificationTg = () => {
user.lastName = 'Name';*/
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;
}