Merge remote-tracking branch 'origin/frontend' into dev
This commit is contained in:
commit
a42428cb53
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
.idea
|
||||
.env
|
||||
.DS_Store
|
||||
|
|
BIN
.DS_Store → frontend/.DS_Store
vendored
BIN
.DS_Store → frontend/.DS_Store
vendored
Binary file not shown.
22
frontend/.eslintrc.js
Normal file
22
frontend/.eslintrc.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: "plugin:react/recommended",
|
||||
overrides: [],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["react", "@typescript-eslint"],
|
||||
rules: {
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{
|
||||
additionalHooks: "(useRecoilCallback|useRecoilTransaction_UNSTABLE)",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
1
frontend/.gitignore
vendored
Normal file
1
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/node_modules
|
12
frontend/Dockerfile
Normal file
12
frontend/Dockerfile
Normal file
|
@ -0,0 +1,12 @@
|
|||
FROM node:16.20.0
|
||||
|
||||
WORKDIR /opt/project
|
||||
|
||||
COPY package.json /opt/project
|
||||
COPY package-lock.json /opt/project
|
||||
|
||||
RUN npm install
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "npm", "run", "dev" ]
|
75
frontend/bin/dev.js
Normal file
75
frontend/bin/dev.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
const webpack = require("webpack");
|
||||
const [webpackClientConfig, webpackServerConfig] = require("../webpack.config");
|
||||
const nodemon = require("nodemon");
|
||||
const path = require("path");
|
||||
const webpackDevMiddleware = require("webpack-dev-middleware");
|
||||
const webpackHotMiddleware = require("webpack-hot-middleware");
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
|
||||
const hmrServer = express();
|
||||
const clientCompiler = webpack(webpackClientConfig);
|
||||
|
||||
const allowedOrigins = ["http://localhost:3000", "http://localhost:3001"];
|
||||
|
||||
hmrServer.use(
|
||||
cors({
|
||||
origin: function (origin, callback) {
|
||||
// allow requests with no origin
|
||||
// (like mobile apps or curl requests)
|
||||
if (!origin) return callback(null, true);
|
||||
if (allowedOrigins.indexOf(origin) === -1) {
|
||||
var msg =
|
||||
"The CORS policy for this site does not " +
|
||||
"allow access from the specified Origin.";
|
||||
return callback(new Error(msg), false);
|
||||
}
|
||||
return callback(null, true);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
hmrServer.use(
|
||||
webpackDevMiddleware(clientCompiler, {
|
||||
publicPath: webpackClientConfig.output.publicPath,
|
||||
serverSideRender: true,
|
||||
noInfo: true,
|
||||
watchOptions: {
|
||||
ignore: /dist/,
|
||||
},
|
||||
writeToDisk: true,
|
||||
stats: "errors-only",
|
||||
})
|
||||
);
|
||||
|
||||
hmrServer.use(
|
||||
webpackHotMiddleware(clientCompiler, {
|
||||
path: "/static/__webpack_hmr",
|
||||
})
|
||||
);
|
||||
|
||||
hmrServer.listen(3001, () => {
|
||||
console.log("Hmr Server successfully started");
|
||||
});
|
||||
|
||||
const compiler = webpack(webpackServerConfig);
|
||||
|
||||
compiler.run((err) => {
|
||||
if (err) {
|
||||
console.log(`compilation failed:`, err);
|
||||
}
|
||||
compiler.watch({}, (err) => {
|
||||
if (err) {
|
||||
console.log(`compilation failed:`, err);
|
||||
}
|
||||
console.log("Compilation was successfully");
|
||||
});
|
||||
|
||||
nodemon({
|
||||
script: path.resolve(__dirname, "../dist/server/server.js"),
|
||||
watch: [
|
||||
path.resolve(__dirname, "../dist/server"),
|
||||
path.resolve(__dirname, "../dist/client"),
|
||||
],
|
||||
});
|
||||
});
|
24
frontend/index.html
Normal file
24
frontend/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<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"
|
||||
content="Clicker"
|
||||
/>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="preconnect" href="/fonts/">
|
||||
<title>Clicker</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="modal_root"></div>
|
||||
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
30312
frontend/package-lock.json
generated
Normal file
30312
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
77
frontend/package.json
Normal file
77
frontend/package.json
Normal file
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "gpnevent",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "vite.config.js",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "16.x"
|
||||
},
|
||||
"scripts": {
|
||||
"predeploy": "npm run build:dev",
|
||||
"deploy": "gh-pages -d build:dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"dev": "vite serve",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Anna Efremova",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@hot-loader/react-dom": "^17.0.1",
|
||||
"@redux-devtools/extension": "^3.2.5",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/intl-tel-input": "^18.1.1",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/react": "^17.0.50",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-linkify": "^1.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"axios": "^1.4.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"express": "^4.17.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"intl-tel-input": "^18.2.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"less": "^3.13.1",
|
||||
"less-loader": "^5.0.0",
|
||||
"nodemon": "^2.0.12",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react-linkify": "^1.0.0-alpha",
|
||||
"react-player": "^2.12.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.11.1",
|
||||
"react-select": "^5.7.3",
|
||||
"redux": "^4.2.1",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"swiper": "^11.1.1",
|
||||
"ts-jest": "^28.0.7",
|
||||
"ts-loader": "^6.2.1",
|
||||
"typescript": "4.6.4",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-middleware": "^3.7.3",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
}
|
||||
}
|
BIN
frontend/public/.DS_Store
vendored
Normal file
BIN
frontend/public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
frontend/public/assets/.DS_Store
vendored
Normal file
BIN
frontend/public/assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-Bold.woff
Normal file
BIN
frontend/public/fonts/Inter-Bold.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-Bold.woff2
Normal file
BIN
frontend/public/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-ExtraBold.woff
Normal file
BIN
frontend/public/fonts/Inter-ExtraBold.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-ExtraBold.woff2
Normal file
BIN
frontend/public/fonts/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-Regular.woff
Normal file
BIN
frontend/public/fonts/Inter-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-Regular.woff2
Normal file
BIN
frontend/public/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-SemiBold.woff
Normal file
BIN
frontend/public/fonts/Inter-SemiBold.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-SemiBold.woff2
Normal file
BIN
frontend/public/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Raleway-Regular.woff
Normal file
BIN
frontend/public/fonts/Raleway-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Raleway-Regular.woff2
Normal file
BIN
frontend/public/fonts/Raleway-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Raleway-SemiBold.woff
Normal file
BIN
frontend/public/fonts/Raleway-SemiBold.woff
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Raleway-SemiBold.woff2
Normal file
BIN
frontend/public/fonts/Raleway-SemiBold.woff2
Normal file
Binary file not shown.
15
frontend/public/manifest.json
Normal file
15
frontend/public/manifest.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "Clicker",
|
||||
"name": "Clicker",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#222222",
|
||||
"background_color": "#222222"
|
||||
}
|
3
frontend/public/robots.txt
Normal file
3
frontend/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
BIN
frontend/src/.DS_Store
vendored
Normal file
BIN
frontend/src/.DS_Store
vendored
Normal file
Binary file not shown.
46
frontend/src/App.tsx
Normal file
46
frontend/src/App.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import './main.global.css';
|
||||
import { hot } from "react-hot-loader/root";
|
||||
import { Layout } from "./shared/Layout";
|
||||
import { applyMiddleware, createStore } from "redux";
|
||||
import { rootReducer } from "./store/reducer";
|
||||
import { composeWithDevTools } from '@redux-devtools/extension';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Route, Routes, BrowserRouter } from "react-router-dom";
|
||||
import { AuctionMainPopups } from "./shared/Auction/AuctionMainPopups";
|
||||
import { RoutePage } from "./shared/Pages/RoutePage";
|
||||
|
||||
const store = createStore(rootReducer, composeWithDevTools(
|
||||
applyMiddleware(thunk)
|
||||
));
|
||||
|
||||
function AppComponent() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
{mounted && (<BrowserRouter>
|
||||
<Layout>
|
||||
<Routes>
|
||||
<Route path='/' element={<RoutePage page='main' />} />
|
||||
<Route path='/rating' element={<RoutePage page='rating' />} />
|
||||
<Route path='/referral' element={<RoutePage page='referral' />} />
|
||||
<Route path='/auction' element={<RoutePage page='auction' />} />
|
||||
<Route path='/styles' element={<RoutePage page='styles' />} />
|
||||
<Route path='/dev' element={<RoutePage page='dev' />} />
|
||||
<Route path='*' element={<RoutePage page='main' />} />
|
||||
</Routes>
|
||||
<AuctionMainPopups />
|
||||
</Layout>
|
||||
</BrowserRouter>)}
|
||||
</Provider>
|
||||
)
|
||||
};
|
||||
|
||||
export const App = hot(() => <AppComponent />);
|
||||
|
BIN
frontend/src/assets/.DS_Store
vendored
Normal file
BIN
frontend/src/assets/.DS_Store
vendored
Normal file
Binary file not shown.
7
frontend/src/client/index.jsx
Normal file
7
frontend/src/client/index.jsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { App } from "../App";
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
ReactDom.hydrate(<App />, document.getElementById("main_root"));
|
||||
});
|
BIN
frontend/src/fonts/Inter-Bold.woff
Normal file
BIN
frontend/src/fonts/Inter-Bold.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-Bold.woff2
Normal file
BIN
frontend/src/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-ExtraBold.woff
Normal file
BIN
frontend/src/fonts/Inter-ExtraBold.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-ExtraBold.woff2
Normal file
BIN
frontend/src/fonts/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-Regular.woff
Normal file
BIN
frontend/src/fonts/Inter-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-Regular.woff2
Normal file
BIN
frontend/src/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-SemiBold.woff
Normal file
BIN
frontend/src/fonts/Inter-SemiBold.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Inter-SemiBold.woff2
Normal file
BIN
frontend/src/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Raleway-Regular.woff
Normal file
BIN
frontend/src/fonts/Raleway-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Raleway-Regular.woff2
Normal file
BIN
frontend/src/fonts/Raleway-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Raleway-SemiBold.woff
Normal file
BIN
frontend/src/fonts/Raleway-SemiBold.woff
Normal file
Binary file not shown.
BIN
frontend/src/fonts/Raleway-SemiBold.woff2
Normal file
BIN
frontend/src/fonts/Raleway-SemiBold.woff2
Normal file
Binary file not shown.
10
frontend/src/index.tsx
Normal file
10
frontend/src/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
import ReactDOM from 'react-dom';
|
||||
import { App } from "./App";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
185
frontend/src/main.global.css
Normal file
185
frontend/src/main.global.css
Normal file
|
@ -0,0 +1,185 @@
|
|||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./fonts/Inter-Regular.woff2') format("woff2"),
|
||||
url('./fonts/Inter-Regular.woff') format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./fonts/Inter-SemiBold.woff2') format("woff2"),
|
||||
url('./fonts/Inter-SemiBold.woff') format("woff");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./fonts/Inter-Bold.woff2') format("woff2"),
|
||||
url('./fonts/Inter-Bold.woff') format("woff");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./fonts/Inter-ExtraBold.woff2') format("woff2"),
|
||||
url('./fonts/Inter-ExtraBold.woff') format("woff");
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: url('./fonts/Raleway-Regular.woff2') format("woff2"),
|
||||
url('./fonts/Raleway-Regular.woff') format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: url('./fonts/Raleway-SemiBold.woff2') format("woff2"),
|
||||
url('./fonts/Raleway-SemiBold.woff') format("woff");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #7EB4DB;
|
||||
--gradientPrimary: linear-gradient(90deg, #90D7ED 13.05%, #6887C4 91.06%, #8085C0 172.24%);
|
||||
--black: #000000;
|
||||
--white: #FFFFFF;
|
||||
--elBackround: #383838;
|
||||
--textColor: #FFFFFF;
|
||||
--textColor2: #8F8F8F;
|
||||
--pink: #FC4848;
|
||||
--red: #FF0000;
|
||||
--blue: #7EB4DB;
|
||||
--purple: #9747FF;
|
||||
--gradientBlue: linear-gradient(90deg, #90D7ED 13.05%, #6887C4 91.06%, #8085C0 172.24%);
|
||||
--gradientOrange: linear-gradient(302deg, #FF5421 -59.57%, #FF7248 43.7%, #FF9576 163.26%);
|
||||
--gradientYellow: linear-gradient(302deg, #6ACB54 -59.57%, #DCBB5A 43.7%, #E2883D 163.26%);
|
||||
--gradientOrangeYellow: linear-gradient(302deg, #FF805A -1.15%, #DEAE53 83.89%);
|
||||
--grey6F: #6F6F6F;
|
||||
--grey6C: #6C6C6C;
|
||||
--grey35: #353535;
|
||||
--grey34: #343434;
|
||||
--grey1B: #1B1B1B;
|
||||
--greyA4: #A4A4A4;
|
||||
--grey46: #464646;
|
||||
--grey12: #121212;
|
||||
--grey19: #191919;
|
||||
--grey77: #777777;
|
||||
--grey24: #242424;
|
||||
--grey22: #222222;
|
||||
--grey9A: #9A9A9A;
|
||||
--grey93: #939393;
|
||||
--grey1F: #1F1F1F;
|
||||
--grey27: #272727;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 120%;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
color: var(--textColor);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
p, h1, h2, h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
line-height: 120%;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
color: var(--textColor);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
font-size: 65px;
|
||||
line-height: 120%;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* slider */
|
||||
|
||||
.swiper-pagination-bullet {
|
||||
margin: 0 2px !important;
|
||||
height: 5px !important;
|
||||
width: 5px !important;
|
||||
background-color: var(--white) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet-active {
|
||||
width: 20px !important;
|
||||
border-radius: 5px !important;
|
||||
background: var(--gradientPrimary) !important;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
19
frontend/src/server/indexTemplate.js
Normal file
19
frontend/src/server/indexTemplate.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
export const indexTemplate = (content) => `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Clicker</title>
|
||||
<script src="/static/client.js" type="application/javascript"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main_root">${content}</div>
|
||||
<div id="modal_root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
`;
|
20
frontend/src/server/server.js
Normal file
20
frontend/src/server/server.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import express from "express";
|
||||
import ReactDOM from "react-dom/server";
|
||||
import { App } from "../App";
|
||||
import { indexTemplate } from "./indexTemplate";
|
||||
import compression from 'compression';
|
||||
const app = express();
|
||||
|
||||
app.use(compression());
|
||||
app.use("/static", express.static("./dist/client"));
|
||||
|
||||
app.get("*", (req, res) => {
|
||||
res.send(indexTemplate(ReactDOM.renderToString(App())));
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`server started on port ${port}`);
|
||||
});
|
||||
|
73
frontend/src/shared/Auction/AuctionCard/AuctionCard.tsx
Normal file
73
frontend/src/shared/Auction/AuctionCard/AuctionCard.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
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';
|
||||
import { formatNumber } from '../../../utils/formatNumber';
|
||||
import { Slider } from '../../Elements/Slider';
|
||||
import { ModalWindow } from '../../ModalWindow';
|
||||
import { AuctionPopup } from '../AuctionPopup';
|
||||
import { ResultAuctionPopup } from '../ResultAuctionPopup';
|
||||
|
||||
interface IAuctionCard {
|
||||
name: string,
|
||||
imgs: Array<string>,
|
||||
minBet: string,
|
||||
users: number,
|
||||
prevBet: string,
|
||||
myBetInit: string,
|
||||
time: number,
|
||||
isLead: boolean
|
||||
}
|
||||
|
||||
export function AuctionCard({ name, imgs, users, prevBet, myBetInit, time, isLead }: IAuctionCard) {
|
||||
const [myBet, setBet] = useState(Number(myBetInit));
|
||||
const [myNewBet, setMyNewBet] = useState(0);
|
||||
const [initPrevBet, setPrevBet] = useState(prevBet);
|
||||
const [initLead, setLead] = useState(isLead);
|
||||
const [closeWindow, setClose] = useState(true);
|
||||
const [closeAnim, setCloseAnim] = useState(false);
|
||||
const [closeresultPopup, setCloseResultPopup] = useState(true);
|
||||
const styleIndex = Number(localStorage.getItem('selectedStyle'));
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} ${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>
|
||||
<div className={`${styles.card} ${styles.cardFlex} ${styles.card1}`}>
|
||||
<p style={ETextStyles.RwRg14100}>Минимальная ставка</p>
|
||||
<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>}
|
||||
</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>
|
||||
<Timer initTime={time}/>
|
||||
</div>
|
||||
<Button onClick={() => setClose(false)} text={myBet === 0 ? 'Сделать первую ставку' : <div className={styles.newBtn}>
|
||||
<p>Увеличить ставку</p>
|
||||
<div className={styles.prevText}>
|
||||
<p style={ETextStyles.InRg12140}>{`Предыдущая ставка — ${formatNumber(myBet)}`}</p>
|
||||
<div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')"}}></div>
|
||||
</div>
|
||||
</div>}
|
||||
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}/>
|
||||
} />}
|
||||
{!closeresultPopup && <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}/>
|
||||
} />}
|
||||
</div>
|
||||
);
|
||||
}
|
116
frontend/src/shared/Auction/AuctionCard/auctioncard.module.css
Normal file
116
frontend/src/shared/Auction/AuctionCard/auctioncard.module.css
Normal file
|
@ -0,0 +1,116 @@
|
|||
.container {
|
||||
padding: 6px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 130px 0px rgba(124, 173, 216, 0.20);
|
||||
}
|
||||
|
||||
.darkContainer {
|
||||
background-color: var(--grey22);
|
||||
}
|
||||
|
||||
.darkContainer .card {
|
||||
background-color: var(--grey12);
|
||||
}
|
||||
|
||||
.opacityContainer {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.opacityContainer .card {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 15px 8px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--grey35);
|
||||
}
|
||||
|
||||
.cardFlex {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card2 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.cardTop {
|
||||
margin-bottom: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.cardLeft {
|
||||
max-width: 187px;
|
||||
}
|
||||
|
||||
.cardLeft p span {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.usersBlock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userCount {
|
||||
margin-left: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--grey35);
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.newBtn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.newBtn p {
|
||||
color: var(--grey35);
|
||||
}
|
||||
|
||||
.prevText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.leadCard {
|
||||
box-shadow: 0px 0px 15px 0px rgba(122, 170, 214, 0.48);
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
.slider {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
aspect-ratio: 340/237;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
1
frontend/src/shared/Auction/AuctionCard/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionCard/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionCard';
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import styles from './auctionlosepopup.module.css';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { ProductCard } from '../ProductCard';
|
||||
import { Button } from '../../Button';
|
||||
|
||||
interface IProduct {
|
||||
name: string,
|
||||
img: string,
|
||||
bet: string
|
||||
}
|
||||
|
||||
interface IAuctionLosePopup {
|
||||
items: Array<IProduct>,
|
||||
setClose(a: boolean): void
|
||||
}
|
||||
|
||||
export function AuctionLosePopup({ items, setClose }: IAuctionLosePopup) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.iconBlock}>
|
||||
<div className={styles.icon} style={{ backgroundImage: "url('assets/Angry.png')" }}></div>
|
||||
</div>
|
||||
<h2 className={styles.title} style={ETextStyles.RwSb24100}>Вы больше не в топе...</h2>
|
||||
<p className={styles.descr} style={ETextStyles.RwSb14120}>Чтобы сохранить лидерство, повысьте свою ставку в аукционе.</p>
|
||||
<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} />
|
||||
})}
|
||||
</div>
|
||||
<Button text='Увеличить ставку' onClick={() => { navigate('/auction'); setClose(true) }} />
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
.iconBlock {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.descr {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cards {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
1
frontend/src/shared/Auction/AuctionLosePopup/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionLosePopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionLosePopup';
|
|
@ -0,0 +1,40 @@
|
|||
import React, { 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';
|
||||
|
||||
export function AuctionMainPopups() {
|
||||
const [closeWin, setCloseWin] = useState(true);
|
||||
const [closeTop, setCloseTop] = useState(true);
|
||||
const [closeLose, setCloseLose] = useState(true);
|
||||
const [closeAnim, setCloseAnim] = useState(false);
|
||||
|
||||
const items = [
|
||||
{
|
||||
name: 'iPhone 15 Pro Max',
|
||||
img: '',
|
||||
bet: '788'
|
||||
},
|
||||
{
|
||||
name: 'iPhone 13 Pro',
|
||||
img: '',
|
||||
bet: '200'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!closeWin && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseWin} removeBtn={true} modalBlock={
|
||||
<AuctionWinPopup name='iPhone 15 Pro Max ' img='' setClose={setCloseAnim}/>
|
||||
} />}
|
||||
{!closeTop && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseTop} removeBtn={true} modalBlock={
|
||||
<AuctionTopPopup items={items} setClose={setCloseAnim}/>
|
||||
} />}
|
||||
{!closeLose && <ModalWindow closeAnimOut={closeAnim} setCloseAnimOut={setCloseAnim} setClose={setCloseLose} removeBtn={true} modalBlock={
|
||||
<AuctionLosePopup items={items} setClose={setCloseAnim} />
|
||||
} />}
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Auction/AuctionMainPopups/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionMainPopups/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionMainPopups';
|
89
frontend/src/shared/Auction/AuctionPopup/AuctionPopup.tsx
Normal file
89
frontend/src/shared/Auction/AuctionPopup/AuctionPopup.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
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';
|
||||
|
||||
interface IAuctionPopup {
|
||||
setClose(a: boolean): void,
|
||||
setLead(a: boolean): void,
|
||||
img: string,
|
||||
name: string,
|
||||
prevBet: string,
|
||||
prevUserImg: string,
|
||||
setBet(a: number): void,
|
||||
setCloseResultPopup(a: boolean): void
|
||||
}
|
||||
|
||||
export function AuctionPopup({ setClose, img, name, prevBet, prevUserImg, setBet, setLead, setCloseResultPopup }: 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 handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let newValue = event.target.value;
|
||||
newValue = newValue.replace(/[^0-9]/g, '');
|
||||
setValue(newValue);
|
||||
|
||||
if (newValue.length != 0) {
|
||||
setDis(false);
|
||||
} else {
|
||||
setDis(true);
|
||||
}
|
||||
}
|
||||
|
||||
const newBet = () => {
|
||||
setBet(Math.floor((1 + percent / 100) * Number(value)));
|
||||
setClose(true);
|
||||
setLead(true);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
setCloseResultPopup(false);
|
||||
clearTimeout(timer);
|
||||
}, 400);
|
||||
|
||||
};
|
||||
|
||||
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>} /> :
|
||||
<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) ?
|
||||
<button className={styles.btnForbidden}>
|
||||
<p style={ETextStyles.InBd14120}>Ставка должна быть больше</p>
|
||||
<p style={ETextStyles.InRg12140} className={styles.textForbidden}>Нельзя сделать ставку меньше предыдущей</p>
|
||||
</button>
|
||||
: <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>
|
||||
<div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')" }}></div>
|
||||
</div>
|
||||
</div>}
|
||||
icon={EIcons.UpPriceIcon} />
|
||||
) :
|
||||
<button className={styles.btnForbidden} onClick={() => navigate('/')}>
|
||||
<p style={ETextStyles.InBd14120}>Тебе не хватает очков</p>
|
||||
<div className={styles.btnText}>
|
||||
<p style={ETextStyles.InRg12140} className={styles.textForbidden}>Нажми сюда, чтобы накопить еще</p>
|
||||
<div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')" }}></div>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
.title {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.btnFirst {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.descr {
|
||||
margin-bottom: 24px;
|
||||
color: var(--grey9A)
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom:16px;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
background-color: var(--grey22);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--grey93);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-bottom: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
.btnText {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.newBtn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.newBtn p {
|
||||
color: var(--grey35);
|
||||
}
|
||||
|
||||
.btnText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.btnForbidden {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 30px;
|
||||
border: 2px solid var(--red);
|
||||
}
|
||||
|
||||
.textForbidden {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btnCancel {
|
||||
margin-bottom: 8px;
|
||||
padding: 14px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 30px;
|
||||
border: 2px solid var(--primary);
|
||||
}
|
1
frontend/src/shared/Auction/AuctionPopup/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionPopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionPopup';
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import styles from './auctiontoppopup.module.css';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { ProductCard } from '../ProductCard';
|
||||
import { Button } from '../../Button';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface IProduct {
|
||||
name: string,
|
||||
img: string,
|
||||
bet: string
|
||||
}
|
||||
|
||||
interface IAuctionTopPopup {
|
||||
items: Array<IProduct>,
|
||||
setClose(a: boolean): void
|
||||
}
|
||||
|
||||
export function AuctionTopPopup({ items, setClose }: IAuctionTopPopup) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.iconBlock}>
|
||||
<div className={styles.icon} style={{ backgroundImage: "url('assets/Fire.png')" }}></div>
|
||||
</div>
|
||||
<h2 className={styles.title} style={ETextStyles.RwSb24100}>Вы в топе</h2>
|
||||
<p className={styles.descr} style={ETextStyles.RwSb14120}>Кликайте, чтобы заработать больше очков и потратить их на ставку в аукционе.</p>
|
||||
<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} />
|
||||
})}
|
||||
</div>
|
||||
<Button text='Продолжить кликать' onClick={() => { navigate('/'); setClose(true)}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
.iconBlock {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.descr {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cards {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
1
frontend/src/shared/Auction/AuctionTopPopup/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionTopPopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionTopPopup';
|
|
@ -0,0 +1,33 @@
|
|||
import React, { useState } from 'react';
|
||||
import styles from './auctionwinpopup.module.css';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { Button } from '../../Button';
|
||||
import { PersonIcon } from '../../Elements/PersonIcon';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ConfettiAnim } from '../Confetti';
|
||||
|
||||
interface IAuctionWinPopup {
|
||||
name: string,
|
||||
img: string,
|
||||
setClose(a: boolean): void
|
||||
}
|
||||
|
||||
export function AuctionWinPopup({ name, img, setClose }: IAuctionWinPopup) {
|
||||
const navigate = useNavigate();
|
||||
const [startConfetti, setStartConfetti] = useState(0);
|
||||
const [removeConfetti, setRemoveConfetti] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.iconBlock}>
|
||||
<div className={styles.icon} style={{backgroundImage: "url('assets/Gift.png')"}}></div>
|
||||
</div>
|
||||
<h2 className={styles.title} style={ETextStyles.RwSb24100}>Вы выиграли в аукционе!</h2>
|
||||
<p className={styles.descr} style={ETextStyles.RwSb14120}>Поздравляем!!! Приз, за который вы так упорно боролись, теперь ваш. Ожидайте сообщение в Telegram от нашей команды.</p>
|
||||
<h3 className={styles.title2} style={ETextStyles.RwSb18120}>Ваш приз</h3>
|
||||
<Button className={styles.productWin} text={name} icon={<PersonIcon img={img} />} onClick={() => setStartConfetti(1)}/>
|
||||
<Button text='Продолжить кликать' stroke={true} onClick={() => { navigate('/'); setClose(true); setRemoveConfetti(true)}}/>
|
||||
{!removeConfetti && <ConfettiAnim opacity={startConfetti} setOpacity={setStartConfetti}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
.iconBlock {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.descr {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.productWin {
|
||||
margin-bottom: 24px;
|
||||
}
|
1
frontend/src/shared/Auction/AuctionWinPopup/index.ts
Normal file
1
frontend/src/shared/Auction/AuctionWinPopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AuctionWinPopup';
|
37
frontend/src/shared/Auction/Confetti/ConfettiAnim.tsx
Normal file
37
frontend/src/shared/Auction/Confetti/ConfettiAnim.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styles from './confetti.module.css';
|
||||
import { useWindowSize } from 'usehooks-ts'
|
||||
import Confetti from 'react-confetti';
|
||||
|
||||
interface IConfettiAnim {
|
||||
opacity: number,
|
||||
setOpacity(a: number): void
|
||||
}
|
||||
|
||||
export function ConfettiAnim({ opacity, setOpacity }: IConfettiAnim) {
|
||||
const node = document.querySelector('#modal_root');
|
||||
const { width, height } = useWindowSize();
|
||||
|
||||
if (!node) return null;
|
||||
|
||||
useEffect(() => {
|
||||
if(opacity === 1) {
|
||||
const timer = setInterval(() => {
|
||||
setOpacity(0);
|
||||
clearInterval(timer);
|
||||
}, 5000);
|
||||
}
|
||||
}, [opacity]);
|
||||
|
||||
return (
|
||||
ReactDOM.createPortal((
|
||||
<Confetti
|
||||
width={width}
|
||||
height={height}
|
||||
className={`${styles.confetti} ${opacity === 1 && styles.animation}`}
|
||||
opacity={opacity}
|
||||
/>
|
||||
), node)
|
||||
);
|
||||
}
|
21
frontend/src/shared/Auction/Confetti/confetti.module.css
Normal file
21
frontend/src/shared/Auction/Confetti/confetti.module.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
.confetti {
|
||||
z-index: 500 !important;
|
||||
}
|
||||
|
||||
.animation {
|
||||
animation: 5s 1 confetti;
|
||||
}
|
||||
|
||||
@keyframes confetti {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
1
frontend/src/shared/Auction/Confetti/index.ts
Normal file
1
frontend/src/shared/Auction/Confetti/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ConfettiAnim';
|
28
frontend/src/shared/Auction/ProductCard/ProductCard.tsx
Normal file
28
frontend/src/shared/Auction/ProductCard/ProductCard.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import styles from './productcard.module.css';
|
||||
import { PersonIcon } from '../../Elements/PersonIcon';
|
||||
import { PointsBlock } from '../../Elements/PointsBlock';
|
||||
import { ETextStyles } from '../../texts';
|
||||
|
||||
interface IProductCard {
|
||||
name: string,
|
||||
img: string,
|
||||
bet: string,
|
||||
personImg ?: boolean | string,
|
||||
className ?: string
|
||||
}
|
||||
|
||||
export function ProductCard({ name, img, bet, personImg=false, className }: IProductCard) {
|
||||
return (
|
||||
<div className={`${styles.card} ${className}`} >
|
||||
<div className={styles.left} style={{ width: `calc(100% - ${55 + bet.length * 9.5}px)` }}>
|
||||
<PersonIcon img={img} className={styles.productImg} />
|
||||
<p style={{ ...ETextStyles.InSb14120 }} className={styles.productName}>{name}</p>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<PointsBlock points={bet} left={true} className={styles.points} />
|
||||
{personImg && typeof personImg === 'string' && <PersonIcon img={personImg} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Auction/ProductCard/index.ts
Normal file
1
frontend/src/shared/Auction/ProductCard/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ProductCard';
|
|
@ -0,0 +1,36 @@
|
|||
.card {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 30px;
|
||||
border: 1px solid var(--grey46);
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.productName {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.points div {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.productImg {
|
||||
flex-shrink: 0;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import styles from './resultauctionpopup.module.css';
|
||||
import { declension } from '../../../utils/declension';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { PointsBlock } from '../../Elements/PointsBlock';
|
||||
import { Button } from '../../Button';
|
||||
|
||||
interface IResultAuctionPopup {
|
||||
prevBet: string,
|
||||
prevMyBet: number,
|
||||
newBet: number,
|
||||
setBet(a: number): void,
|
||||
setClose(a: boolean): void,
|
||||
setCloseBetWindow(a: boolean): void
|
||||
setPrevBet(a: string): void,
|
||||
}
|
||||
|
||||
export function ResultAuctionPopup({ prevBet, prevMyBet, newBet, setBet, setClose, setCloseBetWindow, setPrevBet }: IResultAuctionPopup) {
|
||||
const [diff, setDiff] = useState(0);
|
||||
const [prevBetOld, setPrevBetOld] = useState(prevBet);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setDiff(newBet - prevMyBet);
|
||||
setBet(newBet);
|
||||
setPrevBet(newBet.toString())
|
||||
}, []);
|
||||
|
||||
const onClick = () => {
|
||||
setClose(true);
|
||||
const timer = setInterval(() => {
|
||||
setCloseBetWindow(false);
|
||||
clearInterval(timer);
|
||||
}, 400);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.topIcon}>
|
||||
<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, 'коин', 'коина', 'коинов')}
|
||||
</div>
|
||||
<div className={styles.cards}>
|
||||
<div className={styles.card}>
|
||||
<p style={ETextStyles.RwSb16120}>Сколько вы потратили</p>
|
||||
<PointsBlock left={true} points={newBet.toString()} className={styles.colorPoints}/>
|
||||
</div>
|
||||
<div className={styles.card}>
|
||||
<p style={ETextStyles.RwSb16120}>Предыдущая ставка</p>
|
||||
<PointsBlock left={true} points={prevBetOld.toString()} />
|
||||
</div>
|
||||
</div>
|
||||
<Button text='Увеличить шанс на выигрыш' onClick={() => onClick()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Auction/ResultAuctionPopup/index.ts
Normal file
1
frontend/src/shared/Auction/ResultAuctionPopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ResultAuctionPopup';
|
|
@ -0,0 +1,50 @@
|
|||
.icon {
|
||||
margin-bottom: 12px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.topIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 32px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text span {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.cards {
|
||||
margin-bottom: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 50%;
|
||||
height: 112px;
|
||||
background-color: var(--grey12);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: var(--greyA4);
|
||||
}
|
||||
|
||||
.colorPoints div {
|
||||
color: var(--primary);
|
||||
}
|
53
frontend/src/shared/Auction/Timer/Timer.tsx
Normal file
53
frontend/src/shared/Auction/Timer/Timer.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import styles from './timer.module.css';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { declension } from '../../../utils/declension';
|
||||
import { secondsToHMS } from '../../../utils/secondsToHMS';
|
||||
|
||||
interface ITimer {
|
||||
initTime: number
|
||||
}
|
||||
|
||||
export function Timer({initTime}: ITimer) {
|
||||
const timeValue: Array<number> = secondsToHMS(initTime);
|
||||
const [[hour, min, sec], setTime] = useState([timeValue[0], timeValue[1], timeValue[2]]);
|
||||
const [over, setOver] = useState(false);
|
||||
|
||||
const tick = () => {
|
||||
if (hour === 0 && min === 0 && sec === 0) {
|
||||
setOver(true);
|
||||
} else if (min === 0 && sec === 0) {
|
||||
setTime([hour - 1, 59, 59]);
|
||||
} else if (sec == 0) {
|
||||
setTime([hour, min - 1, 59]);
|
||||
} else {
|
||||
setTime([hour, min, sec - 1]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!over) {
|
||||
const timerID = setInterval(() => tick(), 1000);
|
||||
return () => clearInterval(timerID);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.block}>
|
||||
<p className={styles.value} style={ETextStyles.InSb14120}>{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.text} style={ETextStyles.RwRg12120}>мин</p>
|
||||
</div>
|
||||
<div className={styles.dot}></div>
|
||||
<div className={styles.block}>
|
||||
<p className={styles.value} style={ETextStyles.InSb14120}>{sec.toString().length === 1 ? `0${sec}` : sec}</p>
|
||||
<p className={styles.text} style={ETextStyles.RwRg12120}>сек</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Auction/Timer/index.ts
Normal file
1
frontend/src/shared/Auction/Timer/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Timer';
|
30
frontend/src/shared/Auction/Timer/timer.module.css
Normal file
30
frontend/src/shared/Auction/Timer/timer.module.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.container {
|
||||
width: 110px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 33px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--greyA4);
|
||||
}
|
||||
|
||||
.dot {
|
||||
flex-shrink: 0;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--white);
|
||||
}
|
24
frontend/src/shared/Button/Button.tsx
Normal file
24
frontend/src/shared/Button/Button.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import styles from './button.module.css';
|
||||
import { ETextStyles } from '../texts';
|
||||
|
||||
interface IButton {
|
||||
text: string | boolean | React.ReactNode,
|
||||
className ?: string,
|
||||
onClick?:() => void,
|
||||
icon?: React.ReactNode,
|
||||
disabled?: boolean,
|
||||
stroke?: boolean,
|
||||
}
|
||||
|
||||
export function Button({ className, onClick, text, icon, disabled = false, stroke=false }: IButton) {
|
||||
|
||||
return (
|
||||
<button disabled={disabled} onClick={onClick} className={`${styles.btn} ${className} ${disabled && styles.dis} ${stroke && styles.stroke}`} style={ETextStyles.InSb16120}>
|
||||
<div className={styles.content}>
|
||||
{icon && icon}
|
||||
{text}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
29
frontend/src/shared/Button/button.module.css
Normal file
29
frontend/src/shared/Button/button.module.css
Normal file
|
@ -0,0 +1,29 @@
|
|||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 13px;
|
||||
border-radius: 30px;
|
||||
background: var(--gradientPrimary);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--grey34);
|
||||
}
|
||||
|
||||
.dis {
|
||||
background: var(--greyA4);
|
||||
}
|
||||
|
||||
.stroke {
|
||||
background: none;
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.stroke .content {
|
||||
color: var(--white);
|
||||
}
|
1
frontend/src/shared/Button/index.ts
Normal file
1
frontend/src/shared/Button/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Button';
|
170
frontend/src/shared/Clicker/ClickerBtn/ClickerBtn.tsx
Normal file
170
frontend/src/shared/Clicker/ClickerBtn/ClickerBtn.tsx
Normal file
|
@ -0,0 +1,170 @@
|
|||
import React, { useEffect, 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';
|
||||
|
||||
interface IClickerBtn {
|
||||
coins: number,
|
||||
setCoins(a: number): void,
|
||||
energy: number,
|
||||
setMult(a: number): void,
|
||||
setEnergy(a: number): void
|
||||
}
|
||||
|
||||
export function ClickerBtn({ setCoins, energy, setMult, coins, setEnergy }: IClickerBtn) {
|
||||
const urlClick = useAppSelector<string>(state => state.urlClick);
|
||||
const token = useAppSelector<string>(state => state.token);
|
||||
const [fill, setFill] = useState(0);
|
||||
const [size, setSize] = useState(240);
|
||||
const circumference = 2 * Math.PI * 125;
|
||||
const dashArray = fill * circumference / 100;
|
||||
const img = fill < 100 ? '/assets/imgBtn1.png' : '/assets/imgBtn2.png';
|
||||
const [close, setClose] = useState(true);
|
||||
const [gradient, setGradient] = useState(getGradient());
|
||||
let styleIndex = useAppSelector<number>(state => state.styleIndex);
|
||||
const [maxEnergy, setMaxEnergy] = useState(500);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setFill((maxEnergy - energy) / maxEnergy * 100);
|
||||
}, [energy]);
|
||||
|
||||
useEffect(() => {
|
||||
const savedEnergy = sessionStorage.getItem('eg');
|
||||
if(savedEnergy) {
|
||||
const encodeEnergy = atob(savedEnergy);
|
||||
setMaxEnergy(Number(encodeEnergy));
|
||||
}
|
||||
setFill((maxEnergy - energy) / maxEnergy * 100);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setGradient(getGradient())
|
||||
}, [styleIndex]);
|
||||
|
||||
const btnClick = () => {
|
||||
if (energy != 0) {
|
||||
const newEnergy = energy - 1;
|
||||
const newFill = (maxEnergy - newEnergy) / maxEnergy * 100;
|
||||
const newCoins = coins + 1;
|
||||
dispatch<any>(updateEnergyRequestAsync(newEnergy));
|
||||
setCoins(newCoins);
|
||||
setEnergy(newEnergy)
|
||||
setFill(newFill);
|
||||
|
||||
if (newFill < 100) {
|
||||
setSize(220);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setSize(240);
|
||||
clearTimeout(timer);
|
||||
}, 100);
|
||||
} 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: 'Ты большой молодец',
|
||||
text: <span>Твоя концентрация на высоте, ты перегрел кнопку на 15% сильнее остальных игроков, на столько же уголь Max Flow дает жар сильнее, чем остальные.</span>,
|
||||
img: 'assets/Biceps.png'
|
||||
},
|
||||
{
|
||||
title: 'Как продолжить кликать',
|
||||
text: <span>Чтобы охладиться, нужно закрыть приложение, нажав по кнопке ниже.</span>,
|
||||
img: 'assets/Monocle.png'
|
||||
},
|
||||
{
|
||||
title: 'Что дальше',
|
||||
text: <span>Затем ты сможешь открыть приложение заново и продолжить поддерживать свою большую концентрацию.</span>,
|
||||
img: 'assets/Rocket.png'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.ringContainer}>
|
||||
<div className={`${styles.ringBig} ${fill === 100 && styles.borderNone}`}></div>
|
||||
<div className={`${styles.ringSmall} ${fill === 100 && styles.borderNone}`} style={{ backgroundImage: `url(${img})`, backgroundSize: `${size}px` }} onClick={btnClick}></div>
|
||||
<svg className={styles.svg} width="270" height="270" viewBox="0 0 270 270" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="135" cy="135" r="125" stroke={fill < 100 ? "url(#paint0_linear_619_10702)" : '#FF0000'} strokeWidth="20" strokeLinecap="round" style={{ strokeDasharray: `${dashArray} ${circumference}` }} />
|
||||
<defs>
|
||||
{gradient}
|
||||
</defs>
|
||||
</svg>
|
||||
{!close && <ModalWindow setCloseAnimOut={setClose} removeBtn={true} setClose={setClose} modalBlock={
|
||||
<ClickerPopup title='Кнопка перегрелась' cards={hotCards} setClose={setClose} isBtn={true}/>
|
||||
} />}
|
||||
</div>
|
||||
);
|
||||
}
|
86
frontend/src/shared/Clicker/ClickerBtn/clickerbtn.module.css
Normal file
86
frontend/src/shared/Clicker/ClickerBtn/clickerbtn.module.css
Normal file
|
@ -0,0 +1,86 @@
|
|||
.ringContainer {
|
||||
position: relative;
|
||||
width: 270px;
|
||||
height: 270px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ringContainer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(25px);
|
||||
}
|
||||
|
||||
.ringBig {
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 270px;
|
||||
height: 270px;
|
||||
border: 2px solid var(--white);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ringSmall {
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 230px;
|
||||
height: 230px;
|
||||
border: 2px solid var(--white);
|
||||
border-radius: 50%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.fill {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 267px;
|
||||
height: 267px;
|
||||
border-radius: 50%;
|
||||
background: conic-gradient(red 30%, transparent 0);
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.svg {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.borderNone {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.loadingContainer {
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--grey27);
|
||||
width: 230px;
|
||||
height: 230px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--white);
|
||||
}
|
1
frontend/src/shared/Clicker/ClickerBtn/index.ts
Normal file
1
frontend/src/shared/Clicker/ClickerBtn/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ClickerBtn';
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import styles from './clickerbtnfooter.module.css';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { EIcons, Icon } from '../../Icons';
|
||||
|
||||
interface IClickerBtnFooter {
|
||||
text: string,
|
||||
className ?: string,
|
||||
onClick(): void
|
||||
}
|
||||
|
||||
export function ClickerBtnFooter({ text, className, onClick }: IClickerBtnFooter) {
|
||||
return (
|
||||
<div className={`${styles.container} ${className}`} onClick={onClick}>
|
||||
<div className={styles.content}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
.container {
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/*background-color: var(--grey1B);
|
||||
background-color: rgba(0, 0, 0, 0.6);*/
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--grey34);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--greyA4);
|
||||
}
|
1
frontend/src/shared/Clicker/ClickerBtnFooter/index.ts
Normal file
1
frontend/src/shared/Clicker/ClickerBtnFooter/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ClickerBtnFooter';
|
20
frontend/src/shared/Clicker/ClickerFooter/ClickerFooter.tsx
Normal file
20
frontend/src/shared/Clicker/ClickerFooter/ClickerFooter.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React, { useState } from 'react';
|
||||
import styles from './clickerfooter.module.css';
|
||||
import { ClickerBtnFooter } from '../ClickerBtnFooter';
|
||||
import { EIcons, Icon } from '../../Icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export function ClickerFooter() {
|
||||
const navigate = useNavigate();
|
||||
const isDev = true;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ClickerBtnFooter text='Стили' className={styles.btn} onClick={() => navigate('/styles')}/>
|
||||
<ClickerBtnFooter text='Аукцион' className={styles.btn} onClick={() => { !isDev ? navigate('/auction') : navigate('/dev?type=auction') }}/>
|
||||
{ !isDev && <div className={styles.fire}>
|
||||
<Icon icon={EIcons.FireIcon}/>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
.container {
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
border-radius: 24px;
|
||||
/*background-color: var(--grey35);*/
|
||||
backdrop-filter: blur(25px);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.fire {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--grey46);
|
||||
transform: rotate(10deg);
|
||||
}
|
1
frontend/src/shared/Clicker/ClickerFooter/index.ts
Normal file
1
frontend/src/shared/Clicker/ClickerFooter/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ClickerFooter';
|
42
frontend/src/shared/Clicker/ClickerPopup/ClickerPopup.tsx
Normal file
42
frontend/src/shared/Clicker/ClickerPopup/ClickerPopup.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import styles from './clickerpopup.module.css';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { PopupCard } from '../../Elements/PopupCard';
|
||||
import { generateRandomString } from '../../../utils/generateRandom';
|
||||
import { Button } from '../../Button';
|
||||
|
||||
interface ICardInterface {
|
||||
title: string,
|
||||
text: string | React.ReactNode,
|
||||
img: string
|
||||
}
|
||||
|
||||
interface IClickerPopup {
|
||||
title: string,
|
||||
cards: Array<ICardInterface>,
|
||||
setClose(a: boolean): void,
|
||||
text ?: string,
|
||||
isBtn?: boolean
|
||||
}
|
||||
|
||||
export function ClickerPopup({ title, cards, text, isBtn=false }: IClickerPopup) {
|
||||
|
||||
const blockCards = cards.map((item) => {
|
||||
return <PopupCard key={generateRandomString()} title={item.title} text={item.text} img={item.img}/>
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={ETextStyles.RwSb24100} className={styles.title}>{title}</h2>
|
||||
<div className={styles.cards}>{blockCards}</div>
|
||||
{text && <p className={styles.text}>{text}</p> }
|
||||
{isBtn && <Button text='Закрыть' onClick={() => {
|
||||
//@ts-ignore
|
||||
if (window.Telegram) {
|
||||
//@ts-ignore
|
||||
window.Telegram.WebApp.close()
|
||||
}
|
||||
}}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
.title {
|
||||
margin-bottom: 24px;
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
|
||||
.cards {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: -24px;
|
||||
margin-bottom: 30px;
|
||||
}
|
1
frontend/src/shared/Clicker/ClickerPopup/index.ts
Normal file
1
frontend/src/shared/Clicker/ClickerPopup/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ClickerPopup';
|
105
frontend/src/shared/Clicker/PointsZoom/PointsZoom.tsx
Normal file
105
frontend/src/shared/Clicker/PointsZoom/PointsZoom.tsx
Normal file
|
@ -0,0 +1,105 @@
|
|||
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';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { meRequest, meRequestError, 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';
|
||||
|
||||
interface IPointsZoom {
|
||||
points: number,
|
||||
setClose(a:boolean): void,
|
||||
className ?: string,
|
||||
closePointsAnim: boolean,
|
||||
setClosePointsAnim(a: boolean): void,
|
||||
setCoins(a:number):void,
|
||||
setCloseError(a: boolean): void,
|
||||
setEnergy(a: number): void,
|
||||
setMult(a: number): void,
|
||||
}
|
||||
|
||||
export function PointsZoom({ points, setMult, setClose, setCoins, className, closePointsAnim, setClosePointsAnim, setCloseError, setEnergy }: 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));
|
||||
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 returnEnergy = energy + initPoints;
|
||||
setEnergy(returnEnergy);
|
||||
dispatch<any>(updateEnergyRequestAsync(returnEnergy));
|
||||
dispatch(meRequestError(String(err)));
|
||||
})
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setOpen(false);
|
||||
clearInterval(timer);
|
||||
}, 400);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (closePointsAnim) {
|
||||
sendClicks();
|
||||
const timer = setTimeout(() => {
|
||||
setClosePointsAnim(false);
|
||||
setClose(true);
|
||||
clearTimeout(timer);
|
||||
setCoins(0);
|
||||
}, 400);
|
||||
}
|
||||
}, [closePointsAnim]);
|
||||
|
||||
useEffect(() => {
|
||||
setSizeHand(25);
|
||||
const timer = setTimeout(() => {
|
||||
setSizeHand(30);
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [points]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} ${className} ${open ? styles.animBack : ''} ${closePointsAnim ? styles.animBackClose : ''}`}>
|
||||
{ReactDOM.createPortal((
|
||||
<div className={`${styles.innerContainer} ${open ? styles.animBlock : ''} ${closePointsAnim ? styles.animBlockClose : ''} ${checkIOS() && styles.ios}`}>
|
||||
<div className={styles.icon} style={{ backgroundImage: `url('assets/point.png')`, width: `${sizeHand}px`, height: `${sizeHand}px` }}></div>
|
||||
<p className={styles.point} style={ETextStyles.InSb18100}>{`${formatNumber(Number(points.toFixed(2)))}`}</p>
|
||||
</div>
|
||||
), node)}
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Clicker/PointsZoom/index.ts
Normal file
1
frontend/src/shared/Clicker/PointsZoom/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './PointsZoom';
|
105
frontend/src/shared/Clicker/PointsZoom/pointszoom.module.css
Normal file
105
frontend/src/shared/Clicker/PointsZoom/pointszoom.module.css
Normal file
|
@ -0,0 +1,105 @@
|
|||
.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);
|
||||
height: 50px;
|
||||
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);
|
||||
}
|
||||
|
||||
.ios {
|
||||
top: 40px !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
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;
|
||||
}
|
||||
}
|
35
frontend/src/shared/Clicker/Profile/Profile.tsx
Normal file
35
frontend/src/shared/Clicker/Profile/Profile.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
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';
|
||||
import { useAppSelector } from '../../hooks/useAppSelector';
|
||||
import { Spinner } from '../../Elements/Spinner';
|
||||
|
||||
interface IProfileClicker {
|
||||
name: string,
|
||||
img: string,
|
||||
className ?: string
|
||||
}
|
||||
|
||||
export function Profile({ name, img, className }: IProfileClicker) {
|
||||
const points = useAppSelector<string | undefined>(state => state.me.data.points);
|
||||
const loading = useAppSelector<boolean>(state => state.me.loading);
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} ${className}`}>
|
||||
{img ? <PersonIcon img={`${img}`} size={30} /> : <Icon icon={EIcons.ProfileIcon} /> }
|
||||
<div className={styles.content}>
|
||||
<p style={ETextStyles.RwSb12120} className={styles.name}>{name}</p>
|
||||
<div className={styles.pointsContainer}>
|
||||
{points && <p className={styles.points} style={ETextStyles.InSb10120}>
|
||||
{formatNumber(Number(Number(points).toFixed(2)))}
|
||||
</p>}
|
||||
{!loading && <div className={styles.icon} style={{ backgroundImage: "url('assets/btnIcon.png')"}}></div>}
|
||||
{loading && <Spinner size='14px' color='#FFFFFF' thickness='2px' />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Clicker/Profile/index.ts
Normal file
1
frontend/src/shared/Clicker/Profile/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Profile';
|
50
frontend/src/shared/Clicker/Profile/profile.module.css
Normal file
50
frontend/src/shared/Clicker/Profile/profile.module.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.img {
|
||||
margin-right: 4px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.pointsContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.points {
|
||||
margin-right: 3px;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text !important;
|
||||
background: var(--gradientPrimary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.emptyIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-color: var(--grey12);
|
||||
border-radius: 50%;
|
||||
}
|
89
frontend/src/shared/Clicker/SectionsBlock/SectionsBlock.tsx
Normal file
89
frontend/src/shared/Clicker/SectionsBlock/SectionsBlock.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import styles from './sectionsblock.module.css';
|
||||
import { CardSection } from '../../Elements/CardSection';
|
||||
import { ETextStyles } from '../../texts';
|
||||
import { PointsBlock } from '../../Elements/PointsBlock';
|
||||
import { ModalWindow } from '../../ModalWindow';
|
||||
import { ClickerPopup } from '../ClickerPopup';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { UsersIcons } from '../../Elements/UsersIcons';
|
||||
import { formatNumber } from '../../../utils/formatNumber';
|
||||
import { useAppSelector } from '../../hooks/useAppSelector';
|
||||
|
||||
interface ISectionsBlock {
|
||||
mult:number;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
if(referralStorage >= maxReferralStorage) {
|
||||
serReferralPercent(100);
|
||||
} else {
|
||||
serReferralPercent(referralStorage / maxReferralStorage * 100);
|
||||
}
|
||||
|
||||
}, [referralStorage, maxReferralStorage]);
|
||||
|
||||
const isDev = true;
|
||||
|
||||
const multipCards = [
|
||||
{
|
||||
title: 'Что он делает',
|
||||
text: <span>Увеличивает получение баллов с одного клика в столько раз, сколько указано в рамке.</span>,
|
||||
img: 'assets/Rocket.png'
|
||||
},
|
||||
{
|
||||
title: 'Как его увеличить',
|
||||
text: <span>Чем выше концентрация — клики в час, тем выше множитель, он рассчитывается по формуле.</span>,
|
||||
img: 'assets/Monocle.png'
|
||||
},
|
||||
{
|
||||
title: 'Может ли он уменьшиться',
|
||||
text: <span>Да, если снизится концентрация или долго не заходить в приложение и не совершать клики.</span>,
|
||||
img: 'assets/Chain.png'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.sectionContainer}>
|
||||
<div className={styles.leftContainer}>
|
||||
<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>
|
||||
</div>
|
||||
<UsersIcons size={16}/>
|
||||
</div>}
|
||||
</CardSection>
|
||||
<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={() => { navigate('/referral') }}>
|
||||
{<div>
|
||||
<PointsBlock points={formatNumber(referralStorage.toFixed(2))} className={styles.scalePoints} />
|
||||
<div className={styles.scaleContainer}>
|
||||
<div className={`${styles.scale} ${referralPercent === 100 ? styles.scaleFull : ''}`} style={{ width: `${referralPercent}%` }}></div>
|
||||
</div>
|
||||
<p className={`${styles.scaleText} ${referralPercent === 100 ? styles.textFull : ''}`}>
|
||||
{referralPercent === 100 ? 'Хранилище заполнено, заберите коины' : 'Когда хранилище заполнится, вы сможете забрать баллы'}
|
||||
</p>
|
||||
</div>}
|
||||
</CardSection>
|
||||
{!close && <ModalWindow setCloseAnimOut={setClose} setClose={setClose} modalBlock={
|
||||
<ClickerPopup title='Что такое множитель' cards={multipCards} setClose={setClose}/>
|
||||
} />}
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/shared/Clicker/SectionsBlock/index.ts
Normal file
1
frontend/src/shared/Clicker/SectionsBlock/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './SectionsBlock';
|
|
@ -0,0 +1,81 @@
|
|||
.sectionContainer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.leftContainer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.rigthEl {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.bottomRank {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.rank1 {
|
||||
margin-right: 3px;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text !important;
|
||||
background: var(--gradientPrimary);
|
||||
}
|
||||
|
||||
.scalePoints {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.scaleContainer {
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: var(--grey6F);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scaleText {
|
||||
font-size: 10px;
|
||||
color: var(--grey6C);
|
||||
}
|
||||
|
||||
.textFull {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.scale {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 10px;
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.scaleFull {
|
||||
background: var(--purple);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
17
frontend/src/shared/Clicker/StyleElements/StyleElements.tsx
Normal file
17
frontend/src/shared/Clicker/StyleElements/StyleElements.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import styles from './styleelements.module.css';
|
||||
|
||||
interface IStyleElements {
|
||||
styleIndex: number
|
||||
}
|
||||
|
||||
export function StyleElements({ styleIndex }: IStyleElements) {
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.img} ${styles.img1}`} style={{ backgroundImage: `url("assets/style${styleIndex + 1}.png")`, width: `${styleIndex === 1 ? 73 : (styleIndex === 2 ? 48 : 82)}px`, height: `${styleIndex === 1 ? 68 : (styleIndex === 2 ? 86 : 127)}px`, top: `${styleIndex === 1 ? 2 : (styleIndex === 2 ? -5 : -20)}px`, right: `${styleIndex === 1 ? 10 : (styleIndex === 2 ? 20 : 10)}px`, transform: `rotate(${styleIndex === 1 ? 28 : (styleIndex === 2 ? 43 : 48)}deg)` }}></div>
|
||||
<div className={`${styles.img} ${styles.img2}`} style={{ backgroundImage: `url("assets/style${styleIndex + 1}.png")`, width: `${styleIndex === 1 ? 73 : (styleIndex === 2 ? 78 : 98)}px`, height: `${styleIndex === 1 ? 68 : (styleIndex === 2 ? 139 : 152)}px`, top: `${styleIndex === 1 ? 255 : (styleIndex === 2 ? 180 : 174)}px`, right: `${styleIndex === 1 ? 0 : (styleIndex === 2 ? 3 : -10)}px`, transform: `rotate(${styleIndex === 1 ? 32 : (styleIndex === 2 ? 19 : -114)}deg)`, zIndex: `${styleIndex > 1 ? 2 : 1}` }}></div>
|
||||
<div className={`${styles.img} ${styles.img3}`} style={{ backgroundImage: `url("assets/style${styleIndex + 1}.png")`, width: `${styleIndex === 1 ? 73 : (styleIndex === 2 ? 77 : 98)}px`, height: `${styleIndex === 1 ? 68 : (styleIndex === 2 ? 139 : 152)}px`, top: `${styleIndex === 1 ? 275 : (styleIndex === 2 ? 322 : 325)}px`, left: `${styleIndex === 1 ? -2 : (styleIndex === 2 ? -35 : -40)}px`, transform: `rotate(${styleIndex === 1 ? -36 : (styleIndex === 2 ? -24 : 65)}deg)` }}></div>
|
||||
<div className={`${styles.img} ${styles.img4}`} style={{ backgroundImage: `url("assets/style${styleIndex + 1}.png")`, width: `${styleIndex === 1 ? 73 : (styleIndex === 2 ? 77 : 98)}px`, height: `${styleIndex === 1 ? 68 : (styleIndex === 2 ? 139 : 152)}px`, top: `${styleIndex === 1 ? 530 : (styleIndex === 2 ? 433 : 425)}px`, right: `${styleIndex === 1 ? 55 : (styleIndex === 2 ? -5 : 0)}px`, transform: `rotate(${styleIndex === 1 ? 82 : (styleIndex === 2 ? 19 : 0)}deg)` }}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user