added energy, mult storage, button and profile | also fixed bugs

This commit is contained in:
Arseniy Sitnikov 2024-12-11 05:02:21 +03:00
parent e9e0729a77
commit 26633b4b55
53 changed files with 634 additions and 160 deletions

BIN
frontend/.DS_Store vendored

Binary file not shown.

View File

@ -3,10 +3,8 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#000000" />
<meta
name="description"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

BIN
frontend/src/.DS_Store vendored

Binary file not shown.

View File

@ -32,6 +32,7 @@ function AppComponent() {
<Route path='/referral' element={<RoutePage page='referral' />} />
<Route path='/auction' element={<RoutePage page='auction' />} />
<Route path='/styles' element={<RoutePage page='styles' />} />
<Route path='*' element={<RoutePage page='main' />} />
</Routes>
<AuctionMainPopups />
</Layout>

View File

@ -82,6 +82,7 @@
--grey22: #222222;
--grey9A: #9A9A9A;
--grey93: #939393;
--grey1F: #1F1F1F;
}
body {

View File

@ -8,14 +8,16 @@ import { useDispatch } from 'react-redux';
import { updateCoinsRequestAsync } from '../../../store/me/actions';
import axios from 'axios';
import { DevPopup } from '../../Elements/DevPopup';
import { saveMult } from '../../../store/mult';
interface IClickerBtn {
coins: number,
setCoins(a: number): void,
energy: number
energy: number,
setMult(a: number): void
}
export function ClickerBtn({ coins, setCoins, energy }: IClickerBtn) {
export function ClickerBtn({ coins, setCoins, energy, setMult }: IClickerBtn) {
const urlClick = useAppSelector<string>(state => state.urlClick);
const token = useAppSelector<string>(state => state.token);
const [fill, setFill] = useState(0);
@ -27,14 +29,20 @@ export function ClickerBtn({ coins, setCoins, energy }: IClickerBtn) {
const [gradient, setGradient] = useState(getGradient());
let styleIndex = useAppSelector<number>(state => state.styleIndex);
const [initEnergy, setEnergy] = useState(energy);
const maxEnergy = Number(localStorage.getItem('eg'));
//const maxEnergy = Number(localStorage.getItem('eg'));
const [maxEnergy, setMaxEnergy] = useState(500);
const [closeError, setCloseError] = useState(true);
const [error, setError] = useState(false);
const [animClose, setAnimClose] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
setFill((maxEnergy - initEnergy)/maxEnergy * 100);
const savedEnergy = sessionStorage.getItem('eg');
if(savedEnergy) {
const encodeEnergy = atob(savedEnergy);
setMaxEnergy(Number(encodeEnergy));
}
setFill((maxEnergy - initEnergy) / maxEnergy * 100);
}, []);
useEffect(() => {
@ -42,54 +50,36 @@ export function ClickerBtn({ coins, setCoins, energy }: IClickerBtn) {
}, [styleIndex]);
const btnClick = () => {
sendClick();
/*if(!error) {
if (!(initEnergy === 0)) {
sendClick();
const newEnergy = initEnergy - 1;
const newFill = (maxEnergy - newEnergy) / maxEnergy * 100;
if (newFill <= 100) {
sendClick();
const newCoins = coins + 1;
dispatch<any>(updateCoinsRequestAsync(newCoins, 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);
}
} else {
sendClick();
}*/
setClose(false);
}
};
const sendClick = () => {
if(urlClick && token) {
axios.get(`${urlClick}/api/v1/click`, {
headers: {
//"Content-type": "application/json",
"Authorization": `TelegramToken ${token}`
if(token) {
axios.post(`${urlClick}/api/v1/click/`,
{},
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
},
).then((resp) => {
console.log(resp);
//console.log(resp);
if(resp.data) {
const click = Number(resp.data.click.value);
//
const newEnergy = initEnergy - click;
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 = coins + click;
const newCoins = Number(coins + click);
dispatch<any>(updateCoinsRequestAsync(newCoins, newEnergy))
setCoins(newCoins);
setEnergy(newEnergy)

View File

@ -2,6 +2,7 @@
position: relative;
width: 270px;
height: 270px;
user-select: none;
}
.ringContainer::before {
@ -17,6 +18,7 @@
}
.ringBig {
user-select: none;
position: absolute;
top: 0;
left: 0;
@ -27,6 +29,7 @@
}
.ringSmall {
user-select: none;
z-index: 1;
position: absolute;
top: 50%;

View File

@ -13,7 +13,7 @@ export function ClickerBtnFooter({ text, className, onClick }: IClickerBtnFooter
return (
<div className={`${styles.container} ${className}`} onClick={onClick}>
<div className={styles.content}>
<div className={styles.icon}><Icon icon={EIcons.BitCoinIcon} /></div>
<div className={styles.icon}>{text === 'Аукцион' ? <Icon icon={EIcons.BitCoinIcon} /> :<Icon icon={EIcons.StyleIcon}/>}</div>
<p style={ETextStyles.RwSb14120} className={styles.text}>{text}</p>
</div>
</div>

View File

@ -16,9 +16,9 @@ export function ClickerFooter() {
<div className={styles.container}>
<ClickerBtnFooter text='Стили' className={styles.btn} onClick={() => navigate('/styles')}/>
<ClickerBtnFooter text='Аукцион' className={styles.btn} onClick={() => { !isDev ? navigate('/auction') : setCloseDev(false) }}/>
<div className={styles.fire}>
{ !isDev && <div className={styles.fire}>
<Icon icon={EIcons.FireIcon}/>
</div>
</div>}
{!closeDev && <ModalWindow removeBtn={true} setCloseAnimOut={setCloseAnimOut} closeAnimOut={closeAnimOut} setClose={setCloseDev} modalBlock={
<DevPopup setClose={setCloseAnimOut} type='dev' />
} />}

View File

@ -0,0 +1,47 @@
import React, { useEffect, useState } from 'react';
import styles from './pointszoom.module.css';
import { formatNumber } from '../../../utils/formatNumber';
import { ETextStyles } from '../../texts';
import ReactDOM from 'react-dom';
interface IPointsZoom {
points: number,
setClose(a:boolean): void,
className ?: string,
closePointsAnim: boolean,
setClosePointsAnim(a: boolean): void
}
export function PointsZoom({ points, setClose, className, closePointsAnim, setClosePointsAnim }: IPointsZoom) {
const [open, setOpen] = useState(true);
const node = document.querySelector('#modal_root');
if (!node) return null;
useEffect(() => {
const timer = setInterval(() => {
setOpen(false);
clearInterval(timer);
}, 400);
}, []);
useEffect(() => {
if (closePointsAnim) {
const timer = setTimeout(() => {
setClosePointsAnim(false);
setClose(true);
clearTimeout(timer);
}, 400);
}
}, [closePointsAnim]);
return (
<div className={`${styles.container} ${className} ${open ? styles.animBack : ''} ${closePointsAnim ? styles.animBackClose : ''}`}>
{ReactDOM.createPortal((
<div className={`${styles.innerContainer} ${open ? styles.animBlock : ''} ${closePointsAnim ? styles.animBlockClose : ''}`}>
<div className={styles.icon} style={{ backgroundImage: `url('assets/btnIcon.png')` }}></div>
<p className={styles.point} style={ETextStyles.InSb18100}>{formatNumber(Number(points.toFixed(2)))}</p>
</div>
), node)}
</div>
);
}

View File

@ -0,0 +1 @@
export * from './PointsZoom';

View File

@ -0,0 +1,102 @@
.container {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background: rgba(14, 14, 14, 0.01);
backdrop-filter: blur(2px);
}
.innerContainer {
position: fixed;
top: 20px;
left: 10px;
z-index: 50;
width: calc(100% - 20px);
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 12px;
border-radius: 20px;
background-color: var(--grey1F);
box-shadow: 0px 0px 30px 15px rgba(128, 135, 192, 0.25);
}
.icon {
width: 40px;
height: 40px;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
}
.animBack {
animation-name: animBack;
animation-duration: 0.4s;
animation-iteration-count: 1;
animation-timing-function: ease-in;
}
.animBlock {
animation-name: animBlock;
animation-duration: 0.4s;
animation-iteration-count: 1;
animation-timing-function: ease-in;
}
@keyframes animBack {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes animBlock {
0% {
width: 64px;
}
100% {
width: 100%;
}
}
.animBackClose {
animation-name: animBackClose;
animation-duration: 0.4s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
}
.animBlockClose {
animation-name: animBlockClose;
animation-duration: 0.4s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
}
@keyframes animBackClose {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes animBlockClose {
0% {
width: 100%;
}
100% {
width: 64px;
}
}

View File

@ -3,6 +3,7 @@ import styles from './profile.module.css';
import { ETextStyles } from '../../texts';
import { formatNumber } from '../../../utils/formatNumber';
import { PersonIcon } from '../../Elements/PersonIcon';
import { EIcons, Icon } from '../../Icons';
interface IProfileClicker {
name: string,
@ -14,12 +15,12 @@ interface IProfileClicker {
export function Profile({ name, points, img, className }: IProfileClicker) {
return (
<div className={`${styles.container} ${className}`}>
<PersonIcon img={img} size={30}/>
{img ? <PersonIcon img={`${img}`} size={30}/> : <div className={styles.emptyIcon}><Icon icon={EIcons.ProfileIcon}/></div> }
<div className={styles.content}>
<p style={ETextStyles.RwSb12120} className={styles.name}>{name}</p>
<div className={styles.pointsContainer}>
<p className={styles.points} style={ETextStyles.InSb10120}>
{formatNumber(points)}
{formatNumber(Number(points.toFixed(2)))}
</p>
<div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')"}}></div>
</div>

View File

@ -37,4 +37,14 @@
.content {
margin-left: 4px;
}
.emptyIcon {
display: flex;
align-items: center;
justify-content: center;
width: 25px;
height: 25px;
background-color: var(--grey12);
border-radius: 50%;
}

View File

@ -10,7 +10,11 @@ import { UsersIcons } from '../../Elements/UsersIcons';
import { formatNumber } from '../../../utils/formatNumber';
import { DevPopup } from '../../Elements/DevPopup';
export function SectionsBlock() {
interface ISectionsBlock {
mult:number;
}
export function SectionsBlock({ mult }: ISectionsBlock) {
const scaleRef = 70;
const [close, setClose] = useState(true);
const navigate = useNavigate();
@ -41,20 +45,23 @@ export function SectionsBlock() {
<div className={styles.sectionContainer}>
<div className={styles.leftContainer}>
<CardSection title='Место в топе' onClick={() => {!isDev ? navigate('/rating') : setCloseDev(false)}}>
{!isDev && <div className={styles.bottomRank}>
{<div className={`${styles.bottomRank} ${isDev ? styles.dev : ''}`}>
<div style={ETextStyles.InSb12120}>
<span className={styles.rank1}>#</span>
<span>{formatNumber(12980)}</span>
<span>{formatNumber(1)}</span>
</div>
<UsersIcons size={16}/>
</div>}
</CardSection>
<CardSection title='Множитель' onClick={() => { !isDev ? setClose(false) : setCloseDev(false) }}>
{!isDev &&<PointsBlock points='1.50' />}
<CardSection title='Множитель' onClick={() => { setClose(false) }}>
<p style={ETextStyles.InSb12120}>
<span style={{color: 'var(--primary)'}}>{'X '}</span>
{mult}
</p>
</CardSection>
</div>
<CardSection title='Реферальное хранилище' className={styles.rigthEl} onClick={() => { !isDev ? navigate('/referral') : setCloseDev(false) }}>
{!isDev &&<div>
<CardSection title='Реферальное хранилище' className={styles.rigthEl} onClick={() => { navigate('/referral') }}>
{<div className={isDev ? styles.dev : ''}>
<PointsBlock points={formatNumber(800)} className={styles.scalePoints} />
<div className={styles.scaleContainer}>
<div className={styles.scale} style={{ width: `${scaleRef}px` }}></div>

View File

@ -54,4 +54,20 @@
left: 0;
height: 10px;
background: var(--primary);
}
.dev {
position: relative;
}
.dev::after {
content: '';
position: absolute;
top: -12px;
left: -12px;
width: calc(100% + 24px);
height: calc(100% + 24px);
border-radius: 14px;
background: rgba(20, 20, 20, 0.80);
backdrop-filter: blur(5px);
}

View File

@ -14,9 +14,9 @@ export function DevPopup({ setClose, type }: IDevPopup) {
<div className={styles.iconContainer}>
<div className={styles.icon} style={{backgroundImage: "url('assets/dev.png')"}}></div>
</div>
<h2 className={styles.title} style={ETextStyles.RwSb24100}>{type === 'dev' ? 'Скоро будет доступно' : 'Возникла ошибка'}</h2>
<p className={styles.text} style={ETextStyles.RwSb14120}>{type === 'dev' ? 'Пока что делаем эту фичу. Скоро сможете поюзать.' : 'Мы пока не можем принимать клики, но скоро всё починим.'}</p>
<Button text={type === 'dev' ? 'Продолжить кликать' : 'Принято'} stroke={true} onClick={() => setClose(true)}/>
<h2 className={styles.title} style={ETextStyles.RwSb24100}>{type === 'dev' ? 'Скоро откроем' : 'Возникла ошибка'}</h2>
<p className={styles.text} style={ETextStyles.RwSb14120}>{type === 'dev' ? <span>Подготавливаем что-то особенное для вас. Скоро увидимся на&nbsp;новом уровне!</span> : 'Мы пока не можем принимать клики, но скоро всё починим.'}</p>
<Button text={type === 'dev' ? 'Продолжить кликать' : 'Принято'} onClick={() => setClose(true)}/>
</div>
);
}

View File

@ -73,7 +73,7 @@ export const StylesSwiper: React.FC<IStylesSwiper> = memo(({ selectedStyle, setC
transform: `rotate(${isActive ? 0 : deg}deg)`,
filter: `blur(${isActive ? 0 : 3}px)`
}}
><div className={styles.img}></div></div>
><div className={styles.img} style={{ backgroundImage: `url('assets/style${index + 1}.png')`}}></div></div>
);
}}
</SwiperSlide>))}

View File

@ -24,33 +24,33 @@
background: var(--gradientBlue);
}
.card1 div {
/*.card1 div {
background-image: url('assets/style1.png');
}
}*/
.card2 {
background: var(--gradientOrange);
}
.card2 div {
/*.card2 div {
background-image: url('assets/style2.png');
}
}*/
.card3 {
background: var(--gradientYellow);
}
.card3 div {
/*.card3 div {
background-image: url('assets/style3.png');
}
}*/
.card4 {
background: var(--gradientOrangeYellow);
}
.card4 div {
/*.card4 div {
background-image: url('assets/style4.png');
}
}*/
.disabled {
position: relative;

View File

@ -6,7 +6,7 @@
position: absolute;
top: 0;
border-radius: 50%;
background-color: var(--white);
background-color: var(--grey6C);
border: 1px solid var(--grey12);
}

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import { ArrowIcon, BitCoinIcon, ChartIcon, CopyIcon, FireIcon, SmallCoinIcon, TrendIcon, UpPriceIcon } from './commonIcons';
import { ArrowIcon, BitCoinIcon, ChartIcon, CopyIcon, FireIcon, ProfileIcon, SmallCoinIcon, StyleIcon, TrendIcon, UpPriceIcon } from './commonIcons';
import { MedalFirstIcon, MedalSecondIcon, MedalThirdIcon } from './medals';
interface IconsProps {
icon: EIcons;
@ -17,7 +17,9 @@ export const EIcons = {
TrendIcon: <TrendIcon/>,
ChartIcon: <ChartIcon/>,
CopyIcon: <CopyIcon/>,
UpPriceIcon: <UpPriceIcon/>
UpPriceIcon: <UpPriceIcon/>,
ProfileIcon: <ProfileIcon/>,
StyleIcon: <StyleIcon />
} as const;
type EIcons = typeof EIcons[keyof typeof EIcons];

File diff suppressed because one or more lines are too long

View File

@ -15,11 +15,21 @@ interface IModalWindow {
export function ModalWindow({ modalBlock, setClose, removeBtn, closeAnimOut, setCloseAnimOut }: IModalWindow) {
const node = document.querySelector('#modal_root');
const [closeAnim, setCloseAnim] = useState(false);
const html = document.querySelector('html');
useEffect(() => {
if (html) {
html.style.overflowY = 'hidden';
}
}, []);
if (!node) return null;
const closePopUp = () => {
setCloseAnim(true);
if (html) {
html.style.overflowY = 'auto';
}
const timer = setTimeout(() => {
setClose(true);

View File

@ -6,6 +6,10 @@ import { ETextStyles } from '../../texts';
import { SectionsBlock } from '../../Clicker/SectionsBlock';
import { ClickerFooter } from '../../Clicker/ClickerFooter';
import { StyleElements } from '../../Clicker/StyleElements';
import { PointsZoom } from '../../Clicker/PointsZoom';
import { Timer } from '../../Auction/Timer';
import { useWindowSize } from 'usehooks-ts';
import { useAppSelector } from '../../hooks/useAppSelector';
interface IClickerPageInterface {
name: string,
@ -17,16 +21,42 @@ interface IClickerPageInterface {
export function ClickerPage({ name, points, img, energy }: IClickerPageInterface) {
const styleIndex = Number(localStorage.getItem('selectedStyle'));
const [coins, setCoins] = useState(points);
const [mult, setMult] = useState(1);
const [closePoints, setClosePoints] = useState(true);
const [closePointsAnim, setClosePointsAnim] = useState(false);
const { width, height } = useWindowSize();
const savedMult = useAppSelector<number>(state => state.mult);
useEffect(() => {
setMult(savedMult);
}, [savedMult]);
useEffect(() => {
//@ts-ignore
let timer;
if (points !== coins) {
setClosePoints(false);
timer = setTimeout(() => {
setClosePointsAnim(true);
}, 2000);
}
return () => {
//@ts-ignore
clearTimeout(timer);
};
}, [coins]);
return (
<div className={styles.container}>
<div className={styles.records}>
{!closePoints && <PointsZoom points={coins} setClosePointsAnim={setClosePointsAnim} setClose={setClosePoints} className={styles.pointsAnim} closePointsAnim={closePointsAnim}/>}
<Profile name={name} points={coins} className={styles.profile} img={img}/>
<h1 style={ETextStyles.RwSb24100} className={styles.title}>Мои рекорды</h1>
<SectionsBlock />
<SectionsBlock mult={mult}/>
</div>
<div className={styles.clicker}>
<ClickerBtn coins={coins} setCoins={setCoins} energy={energy}/>
<div className={styles.clicker} style={{height: `${height > 670 && 'calc(100vh - 355px)'}`}}>
<ClickerBtn coins={coins} setCoins={setCoins} energy={energy} setMult={setMult}/>
</div>
<ClickerFooter />
{styleIndex != 0 && <div>

View File

@ -30,7 +30,7 @@
.clicker {
position: relative;
z-index: 5;
margin-bottom: 24px;
margin-bottom: 100px;
display: flex;
align-items: center;
justify-content: center;
@ -39,3 +39,10 @@
.profile {
margin-bottom: 8px;
}
/*.pointsAnim {
position: absolute;
width: 100%;
top: 0;
left: 0;
}*/

View File

@ -12,6 +12,7 @@ import { useUserData } from '../../hooks/useUserData';
import { Spinner } from '../../Elements/Spinner';
import { updateBackground } from '../../../utils/updateBackground';
import { ErrorPage } from '../ErrorPage';
import { useNavigate } from 'react-router-dom';
interface IRoutePage {
page: string
@ -20,26 +21,37 @@ interface IRoutePage {
export function RoutePage({ page }: IRoutePage) {
const verified = useTgData();
const { dataUser, loadingUser, errorUser } = useUserData();
const navigate = useNavigate();
//@ts-ignore
const tg = window.Telegram.WebApp;
var BackButton = tg.BackButton;
useEffect(() => {
updateBackground(page);
updateStyles();
if(page === 'main') {
BackButton.hide();
} else {
BackButton.show();
}
}, [page]);
//{!verified ? <WrongSourcePage/> :
//}
BackButton.onClick(function () {
navigate(-1);
});
return (
<div>
<div>
{page === 'main' && !loadingUser && !errorUser && dataUser.name && dataUser.avatar && <ClickerPage name={dataUser.name} points={Number(dataUser.points)} img={dataUser.avatar} energy={Number(dataUser.energy)}/>}
{!verified ? <WrongSourcePage /> : <div>
{ //@ts-ignore
page === 'main' && !loadingUser && !errorUser && dataUser.name && <ClickerPage name={dataUser.name} points={Number(dataUser.points)} img={dataUser.avatar} energy={Number(dataUser.energy)}/>}
{page === 'rating' && !loadingUser && !errorUser && <RatingPage />}
{page === 'referral' && !loadingUser && !errorUser && <StoragePage />}
{page === 'auction' && !loadingUser && !errorUser && <AuctionPage />}
{page === 'styles' && !loadingUser && !errorUser && <StylesPage />}
{(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

@ -2,30 +2,41 @@ import React, { useEffect, useState } from 'react';
import styles from './storagepage.module.css';
import { ETextStyles } from '../../texts';
import { StorageBtn } from '../../Storage/StorageBtn';
import { StorageScale } from '../../Storage/StorageScale';
import { PopupCard } from '../../Elements/PopupCard';
import { Button } from '../../Button';
import { EIcons, Icon } from '../../Icons';
import { сopyTextToClipboard } from '../../../utils/copyText';
import { Notification } from '../../Notification';
import { StoragePageBlock } from '../../Storage/StoragePageBlock';
import { FriendsPageBlock } from '../../Storage/FriendsPageBlock';
import { useAppSelector } from '../../hooks/useAppSelector';
import { DevPopup } from '../../Elements/DevPopup';
import { ModalWindow } from '../../ModalWindow';
export function StoragePage() {
const userId = useAppSelector<string>(state => state.userTg.id);
const [page, setPage] = useState('storage');
const refLink = 'https://open.spotify.com/';
const refLink = `https://t.me/sapphirecrown_bot?start=user_${userId}`;
const [showNotif, setShow] = useState(false);
const [closeAnimOut, setCloseAnimOut] = useState(false);
const [closeDev, setCloseDev] = useState(true);
return (
<div>
<h1 style={ETextStyles.RwSb30100} className={styles.title}>Реферальная программа</h1>
<div className={styles.btnGroup}>
<StorageBtn active={page === 'storage'} type={'storage'} onClick={() => setPage('storage')}/>
<StorageBtn active={page === 'friends'} type={'friends'} onClick={() => setPage('friends')} />
<StorageBtn isDev={true} active={page === 'friends'} type={'friends'} onClick={() => {
setCloseDev(false);
//setPage('friends')
}
} />
</div>
{page === 'storage' ? <StoragePageBlock/> : <FriendsPageBlock/>}
<Button className={styles.btn} icon={<Icon icon={EIcons.CopyIcon} />} text='Пригласить друга' onClick={() => { сopyTextToClipboard(refLink); setShow(true)}}/>
{showNotif && <Notification title='Успешно' text='Пригласительная ссылка скопирована' setShow={setShow} />}
{!closeDev && <ModalWindow removeBtn={true} setCloseAnimOut={setCloseAnimOut} closeAnimOut={closeAnimOut} setClose={setCloseDev} modalBlock={
<DevPopup setClose={setCloseAnimOut} type='dev' />
} />}
</div>
);
}

View File

@ -8,9 +8,11 @@ export function WrongSourcePage() {
const { width, height } = useWindowSize();
return (
<div className={styles.container} style={{height: `${height}px`}}>
<h1 style={ETextStyles.RwSb24100} className={styles.title}>Похоже вы вошли не по той ссылке...</h1>
<Button text='Войти через Telegram'/>
<div className={styles.container} style={{ height: `${height}px` }}>
<div className={styles.innerContainer}>
<h1 style={ETextStyles.RwSb24100} className={styles.title}>Похоже вы вошли не по той ссылке...</h1>
<Button text='Войти через Telegram' onClick={() => document.location.href = 'https://t.me/sapphirecrown_bot'}/>
</div>
</div>
);
}

View File

@ -4,7 +4,15 @@
align-items: center;
justify-content: center;
flex-direction: column;
}
.innerContainer {
max-width: 400px;
gap: 30px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.title {

View File

@ -6,14 +6,15 @@ import { EIcons, Icon } from '../../Icons';
interface IStorageBtn {
type: 'storage' | 'friends',
active: boolean,
onClick(): void
onClick(): void,
isDev ?: boolean,
}
export function StorageBtn({ type, active, onClick }: IStorageBtn) {
export function StorageBtn({ type, active, onClick, isDev=false }: IStorageBtn) {
const selectedStyle = Number(localStorage.getItem('selectedStyle'));
return (
<button className={`${styles.container} ${active ? styles.fill : styles.stroke}`} style={ETextStyles.RwSb12120} onClick={onClick}>
<button className={`${styles.container} ${active ? styles.fill : styles.stroke} ${isDev ? styles.dev : ''}`} style={ETextStyles.RwSb12120} onClick={onClick}>
<div className={styles.content}>
{type === 'storage' ? <Icon icon={EIcons.TrendIcon} /> : <Icon icon={EIcons.ChartIcon} />}
<p className={selectedStyle === 2 ? styles.darkText : ''}>{type === 'storage' ? 'Хранилище' : 'Друзья'}</p>

View File

@ -22,5 +22,21 @@
}
.stroke {
border: 1px solid var(--primary);
/*border: 1px solid var(--primary);*/
border: 1px solid var(--grey6C);
}
.dev {
position: relative;
}
.dev::after {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgb(50, 50, 50, 0.5);
border-radius: 30px;
}

View File

@ -1,22 +1,33 @@
import React from 'react';
import React, { useState } from 'react';
import styles from './storagepageblock.module.css';
import { StorageScale } from '../StorageScale';
import { ETextStyles } from '../../texts';
import { PopupCard } from '../../Elements/PopupCard';
import { ModalWindow } from '../../ModalWindow';
import { DevPopup } from '../../Elements/DevPopup';
export function StoragePageBlock() {
const [closeAnimOut, setCloseAnimOut] = useState(false);
const [closeDev, setCloseDev] = useState(true);
const isDev = true;
return (
<div>
<h2 style={ETextStyles.RwSb18120} className={styles.title}>Хранилище</h2>
<StorageScale points='400' percent={70} className={styles.scale} />
<p style={ETextStyles.RwRg10120} className={styles.descr}>
В&nbsp;хранилище приходит часть коинов, заработанная вашими друзьями. Считаем так: количество коинов * 5%. Хранилище пополняется каждый вечер.
</p>
<div className={`${styles.containerStorage}`} onClick={() => setCloseDev(false)}>
<StorageScale points='0' percent={0} className={styles.scale} isDev={true}/>
{!isDev && <p style={ETextStyles.RwRg10120} className={styles.descr}>
В&nbsp;хранилище приходит часть коинов, заработанная вашими друзьями. Считаем так: количество коинов * 5%. Хранилище пополняется каждый вечер.
</p>}
{isDev && <div style={{height: '30px'}}></div> }
</div>
<h2 style={ETextStyles.RwSb18120} className={styles.title}>Как пригласить друга?</h2>
<div className={styles.cards}>
<PopupCard img='assets/Chain.png' title='Отправляй ссылку другу' text={<span>Друг присоединяется по&nbsp;пригласительной ссылке и&nbsp;становится рефералом, как только совершает активность в&nbsp;приложении.</span>} />
<PopupCard img='assets/Money.png' title='Зарабатывайте вместе' text={<span>Друг кликает, ты&nbsp;получаешь&nbsp;5% его кликов, а&nbsp;он&nbsp;3% с&nbsp;твоих. Не&nbsp;забывай забирать коины из&nbsp;хранилища!</span>} />
</div>
{!closeDev && <ModalWindow removeBtn={true} setCloseAnimOut={setCloseAnimOut} closeAnimOut={closeAnimOut} setClose={setCloseDev} modalBlock={
<DevPopup setClose={setCloseAnimOut} type='dev' />
} />}
</div>
);
}

View File

@ -16,4 +16,21 @@
display: flex;
flex-direction: column;
gap: 8px;
}
}
.containerStorage {
position: relative;
}
.dev {
z-index: 3;
position: absolute;
width: calc(100% + 10px);
height: calc(100% + 10px);
top: -2px;
left: -5px;
border-radius: 25px;
background-color: rgb(37, 37, 37, 0.2);;
backdrop-filter: blur(4px);
}

View File

@ -8,9 +8,10 @@ interface IStorageScale {
percent: number,
points: string,
className ?: string,
isDev?: boolean,
}
export function StorageScale({ percent, points, className }: IStorageScale) {
export function StorageScale({ percent, points, className, isDev=false }: IStorageScale) {
const [showNotif, setShow] = useState(false);
const [initpercent, setPercent] = useState(percent);
@ -26,8 +27,9 @@ export function StorageScale({ percent, points, className }: IStorageScale) {
<div className={styles.content} style={ETextStyles.InSb16120}>
{initpercent ===100 && <p>Забрать</p>}
{initpercent > 0 && <PointsBlock points={points} sizeIcon={20} sizeText={16} />}
{initpercent === 0 && <div className={styles.imgVolt} style={{backgroundImage: "url('assets/Volt.png')"}}></div>}
{initpercent === 0 && <p style={ETextStyles.InRg14120}>Больше друзей быстрее заполнение</p> }
{initpercent === 0 && !isDev && <div className={styles.imgVolt} style={{backgroundImage: "url('assets/Volt.png')"}}></div>}
{initpercent === 0 && !isDev && <p style={ETextStyles.InRg14120}>Больше друзей быстрее заполнение</p> }
{isDev && <p style={ETextStyles.InRg14120}>Скоро откроем</p>}
</div>
<div className={styles.scale} style={{ width: `${initpercent}%`}}></div>
{showNotif && <Notification title='Пополнение' text={`Баланс баллов увеличен на ${points}`} setShow={setShow} />}

View File

@ -0,0 +1,23 @@
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { ThunkDispatch } from 'redux-thunk';
import { RootState } from '../../store/reducer';
import { Action } from 'redux';
import { useAppSelector } from './useAppSelector';
import { saveReferral } from '../../store/referral';
export function useReferral() {
const dispatch = useDispatch<ThunkDispatch<RootState, unknown, Action>>();
const savedReferral = useAppSelector<string>(state => state.token);
useEffect(() => {
if (savedReferral.length === 0) {
const currentUrl = new URL(window.location.href);
const referredBy = currentUrl.searchParams.get("referred_by");
if (referredBy) {
dispatch<any>(saveReferral(referredBy))
}
}
}, [savedReferral]);
}

View File

@ -17,8 +17,6 @@ export function useTgData() {
if (savedToken.length === 0) {
//@ts-ignore
const [user, token]: [IUserTg, string] = verificationTg();
console.log(`user из useTgData ${user}`);
console.log(`token3 ${token}`);
if (token.length != 0 && user.id && user.id.length != 0) {
setVerified(true);
dispatch<any>(saveToken(token));

View File

@ -3,20 +3,29 @@ import { IUserData, meRequestAsync } from "../../store/me/actions";
import { useEffect } from 'react';
import { useAppSelector } from './useAppSelector';
import { useNavigate } from 'react-router-dom';
import { saveReferral } from '../../store/referral';
export function useUserData() {
const dataUser = useAppSelector<IUserData>(state => state.me.data);
const loadingUser = useAppSelector<boolean>(state => state.me.loading);
const errorUser = useAppSelector<String>(state => state.me.error);
const token = useAppSelector<string>(state => state.token);
const savedReferral = useAppSelector<string>(state => state.token);
const dispatch = useDispatch();
const navigate = useNavigate();
useEffect(() => {
//if (!token) navigate('/auth/welcome');
//@ts-ignore
dispatch(meRequestAsync());
if (savedReferral.length === 0) {
const currentUrl = new URL(window.location.href);
const referredBy = currentUrl.searchParams.get("referred_by");
if (referredBy) {
dispatch<any>(saveReferral(referredBy))
}
}
if (dataUser.username?.length != 0) {
//@ts-ignore
dispatch(meRequestAsync());
}
}, [token]);
return { dataUser, loadingUser, errorUser };

View File

@ -2,6 +2,7 @@ import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "../reducer";
import axios from "axios";
import { saveMult } from "../mult";
export interface IUserData {
tgId?: number;
@ -54,10 +55,33 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
const token = getState().token;
const URL = getState().url;
const URLClick = getState().urlClick;
//localStorage.setItem('eg', '500');
/*if (tgId && URL && !meData.avatar && token.length != 0 && URLClick) {
const referral = getState().referral;
const firstClick = (token: string) => {
axios.post(`${URLClick}/api/v1/click/`,
{},
{
headers: {
"Authorization": `TelegramToken ${token}`
}
}
).then(resp => {
const click = Number(resp.data.click.value);
dispatch<any>(saveMult(click));
const clickCode = btoa(click.toString());
sessionStorage.setItem('mt', clickCode);
});
};
if (tgId && !meData.username && token.length != 0) {
dispatch(meRequest());
axios.get(`${URL}/api/v1/users/${tgId}/`, {
let urlUser = '';
if (referral.length != 0) {
urlUser = `${URL}/api/v1/users/${tgId}?referred_by=${referral}`;
} else {
urlUser = `${URL}/api/v1/users/${tgId}/`;
}
axios.get(urlUser, {
headers: {
"Content-type": "application/json",
"Authorization": `TelegramToken ${token}`
@ -65,42 +89,60 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
},
).then((resp) => {
const user = resp.data;
const encodeToken = btoa(unescape(encodeURIComponent(token)));
const savedToken = localStorage.getItem('sts');
axios.get(`${URLClick}/api/v1/energy`, {
headers: {
//"Content-type": "application/json",
"Authorization": `TelegramToken ${token}`
}
},
).then((resp) => {
const energy = resp.data.energy;
if (savedToken) {
if (savedToken != encodeToken) {
localStorage.setItem('eg', '200'); //enegry
localStorage.setItem('sts', encodeToken);
}
headers: {
//"Content-type": "application/json",
"Authorization": `TelegramToken ${token}`
}
},
).then((resp) => {
const energy = resp.data.energy;
//
const encodeToken = btoa(unescape(encodeURIComponent(token)));
const savedToken = sessionStorage.getItem('tk');
if (savedToken) {
if (savedToken != encodeToken) {
sessionStorage.setItem('tk', encodeToken);
firstClick(token);
const energyCode = btoa(energy.toString());
sessionStorage.setItem('eg', energyCode);
} else {
const mult = sessionStorage.getItem('mt');
if (mult) {
const encodeMult = atob(mult);
dispatch<any>(saveMult(Number(encodeMult)));
} else {
localStorage.setItem('eg', '200'); //energy
localStorage.setItem('sts', encodeToken);
firstClick(token);
}
const userData = {
tgId: user.tg_id,
username: user.username,
avatar: user.avatar,
energy: energy.toString(), //energy
points: user.points,
name: `${firstName} ${secondName}`
};
dispatch(meRequestSuccess(userData));
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(meRequestError(String(err.response.data.detail)));
} else {
dispatch(meRequestError(String(err)));
}
})
}
} else {
sessionStorage.setItem('tk', encodeToken);
firstClick(token);
const energyCode = btoa(energy.toString());
sessionStorage.setItem('eg', energyCode);
}
//
let avatar = user.avatar;
if (!avatar) {
avatar = '';
}
const userData = {
tgId: user.tg_id,
username: user.username,
avatar: user.avatar,
energy: energy.toString(), //energy
points: user.points,
name: `${firstName} ${secondName}`
};
dispatch(meRequestSuccess(userData));
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
dispatch(meRequestError(String(err.response.data.detail)));
} else {
dispatch(meRequestError(String(err)));
}
})
}).catch((err) => {
console.log(err);
if (err.response.data.detail) {
@ -109,8 +151,8 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
dispatch(meRequestError(String(err)));
}
})
}*/
if (tgId && URL && !meData.avatar) {
}
/*if (tgId && URL && !meData.username) {
axios.get(`${URL}/api/v1/users/get-token/123456`, {
headers: {
"Content-type": "application/json"
@ -119,9 +161,15 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
).then((resp) => {
const token = resp.data.token;
getState().token = token;
if (token && !meData.avatar) {
if (token && !meData.username) {
dispatch(meRequest());
axios.get(`${URL}/api/v1/users/${tgId}/`, {
let urlUser = '';
if (referral.length != 0) {
urlUser = `${URL}/api/v1/users/${tgId}?referred_by=${referral}`;
} else {
urlUser = `${URL}/api/v1/users/${tgId}/`;
}
axios.get(urlUser, {
headers: {
"Content-type": "application/json",
"Authorization": `TelegramToken ${token}`
@ -129,8 +177,11 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
},
).then((resp) => {
const user = resp.data;
const encodeToken = btoa(unescape(encodeURIComponent(token)));
const savedToken = localStorage.getItem('sts');
let avatar = user.avatar;
avatar = null;
if (!avatar) {
avatar = '';
}
axios.get(`${URLClick}/api/v1/energy`, {
headers: {
//"Content-type": "application/json",
@ -139,19 +190,35 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
},
).then((resp) => {
const energy = resp.data.energy;
//
const encodeToken = btoa(unescape(encodeURIComponent(token)));
const savedToken = sessionStorage.getItem('tk');
if (savedToken) {
if (savedToken != encodeToken) {
localStorage.setItem('eg', '200'); //enegry
localStorage.setItem('sts', encodeToken);
sessionStorage.setItem('tk', encodeToken);
firstClick(token);
const energyCode = btoa(energy.toString());
sessionStorage.setItem('eg', energyCode);
} else {
const mult = sessionStorage.getItem('mt');
if (mult) {
const encodeMult = atob(mult);
dispatch<any>(saveMult(Number(encodeMult)));
} else {
firstClick(token);
}
}
} else {
localStorage.setItem('eg', '200'); //energy
localStorage.setItem('sts', encodeToken);
sessionStorage.setItem('tk', encodeToken);
firstClick(token);
const energyCode = btoa(energy.toString());
sessionStorage.setItem('eg', energyCode);
}
//
const userData = {
tgId: user.tg_id,
username: user.username,
avatar: user.avatar,
avatar: avatar,
energy: energy.toString(), //user.energy
points: user.points,
name: `${firstName} ${secondName}`
@ -178,7 +245,7 @@ export const meRequestAsync = (): ThunkAction<void, RootState, unknown, Action<s
console.log(err);
dispatch(meRequestError(String(err)));
})
}
}*/
}

View File

@ -0,0 +1,19 @@
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "./reducer";
export const SET_MULT = 'SET_MULT';
export type SetMultAction = {
type: typeof SET_MULT,
mult: number
};
export const setMult: ActionCreator<SetMultAction > = (mult: number) => ({
type: SET_MULT,
mult
});
export const saveMult = (mult: number): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
dispatch(setMult(mult));
}

View File

@ -3,6 +3,8 @@ import { SET_TOKEN } from './token';
import { IUserTg, SET_USER_TG } from './userTg';
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';
export type RootState = {
url: string,
@ -11,7 +13,9 @@ export type RootState = {
token: string,
userTg: IUserTg,
styleIndex: number,
me: MeState
me: MeState,
referral: string,
mult: number
};
//'http://127.0.0.1:8000'
@ -33,6 +37,8 @@ const initialState: RootState = {
error: '',
data: {}
},
referral: '',
mult: 1
};
export const RESET_STATE = 'RESET_STATE';
@ -53,6 +59,16 @@ export const rootReducer: Reducer<RootState> = (state = initialState, action) =>
...state,
userTg: action.userTg
};
case SET_REFERRAL:
return {
...state,
referral: action.referral
};
case SET_MULT:
return {
...state,
mult: action.mult
};
case ME_REQUEST:
case ME_REQUEST_SUCCESS:
case ME_REQUEST_ERROR:

View File

@ -0,0 +1,22 @@
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState } from "./reducer";
export const SET_REFERRAL = 'SET_REFERRAL';
export type SetReferralAction = {
type: typeof SET_REFERRAL,
referral: string
};
export const setReferral: ActionCreator<SetReferralAction > = (referral: string) => ({
type: SET_REFERRAL,
referral
});
export const saveReferral = (referral: string): ThunkAction<void, RootState, unknown, Action<string>> => (dispatch, getState) => {
const savedReferral = getState().referral;
if(savedReferral.length === 0) {
dispatch(setReferral(referral));
}
}

View File

@ -5,11 +5,10 @@ export const verificationTg = () => {
lastName: ''
};
let token = '';
console.log(`window.Telegram из verification ${window.Telegram}`);
if(window.Telegram) {
const tg = window.Telegram.WebApp;
tg.BackButton.show();
tg.expand();
tg.setBackgroundColor("#222222");
const tgData = tg.initDataUnsafe;
@ -41,13 +40,10 @@ export const verificationTg = () => {
token = btoa(
unescape(encodeURIComponent(validation))
);
console.log(`token1 ${token}`);
user.id = tg.initDataUnsafe?.user?.id;
user.firstName = tg.initDataUnsafe?.user?.first_name;
user.lastName = tg.initDataUnsafe?.user?.last_name;
}
console.log(`token2 ${token}`);
//локально
/*token = "TelegramToken";