From 212b58c072645e1b3d54e1844e2e8fcfd9a7eedb Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Mon, 21 Jul 2025 10:55:55 +0500 Subject: [PATCH] add: registration page --- assets/icons/popa-popa.png | Bin 0 -> 6895 bytes assets/icons/popa-popa.svg | 18 ++ package-lock.json | 19 ++ package.json | 1 + src/renderer/api.ts | 60 +++++ src/renderer/pages/Registration.tsx | 375 +++++++++++++++++++++++++++- 6 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 assets/icons/popa-popa.png create mode 100644 assets/icons/popa-popa.svg diff --git a/assets/icons/popa-popa.png b/assets/icons/popa-popa.png new file mode 100644 index 0000000000000000000000000000000000000000..6dd5b8c68942f01029892a3b9501114e71a4ac6e GIT binary patch literal 6895 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 literal 0 HcmV?d00001 diff --git a/assets/icons/popa-popa.svg b/assets/icons/popa-popa.svg new file mode 100644 index 0000000..6000dfc --- /dev/null +++ b/assets/icons/popa-popa.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 0ea2738..ad8cceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "find-java-home": "^2.0.0", "https-browserify": "^1.0.0", "path-browserify": "^1.0.1", + "qr-code-styling": "^1.9.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.3.0", @@ -18136,6 +18137,24 @@ ], "license": "MIT" }, + "node_modules/qr-code-styling": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.2.tgz", + "integrity": "sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==", + "license": "MIT", + "dependencies": { + "qrcode-generator": "^1.4.4" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/qrcode-generator": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz", + "integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", diff --git a/package.json b/package.json index 8e687ee..42fd2e2 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "find-java-home": "^2.0.0", "https-browserify": "^1.0.0", "path-browserify": "^1.0.1", + "qr-code-styling": "^1.9.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.3.0", diff --git a/src/renderer/api.ts b/src/renderer/api.ts index a240ac3..1f2caee 100644 --- a/src/renderer/api.ts +++ b/src/renderer/api.ts @@ -169,6 +169,66 @@ export interface OperationsResponse { operations: MarketplaceOperation[]; } +export interface RegisterUserResponse { + status: string; + uuid: string; +} + +export interface GenerateVerificationCodeResponse { + status: string; + code: string; +} + +export interface VerificationStatusResponse { + is_verified: boolean; +} + +export async function getVerificationStatus( + username: string, +): Promise { + const response = await fetch( + `${API_BASE_URL}/auth/verification_status/${username}`, + ); + if (!response.ok) { + throw new Error('Не удалось получить статус верификации'); + } + return await response.json(); +} + +export async function generateVerificationCode( + username: string, +): Promise { + const response = await fetch( + `${API_BASE_URL}/auth/generate_code?username=${username}`, + { + method: 'POST', + }, + ); + if (!response.ok) { + throw new Error('Не удалось сгенерировать код верификации'); + } + return await response.json(); +} +export async function registerUser( + username: string, + password: string, +): Promise { + const response = await fetch(`${API_BASE_URL}/auth/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + }), + }); + if (!response.ok) { + throw new Error('Не удалось зарегистрировать пользователя'); + } + return await response.json(); +} + export async function getPlayerInventory( request_id: string, ): Promise { diff --git a/src/renderer/pages/Registration.tsx b/src/renderer/pages/Registration.tsx index d8d8bcf..5e747fd 100644 --- a/src/renderer/pages/Registration.tsx +++ b/src/renderer/pages/Registration.tsx @@ -1,3 +1,376 @@ +import Stepper from '@mui/material/Stepper'; +import Step from '@mui/material/Step'; +import StepLabel from '@mui/material/StepLabel'; +import { useEffect, useRef, useState } from 'react'; +import { + StepConnector, + stepConnectorClasses, + StepIconProps, + styled, + Typography, + Box, + TextField, + Button, + Snackbar, + CircularProgress, +} from '@mui/material'; +import LoginRoundedIcon from '@mui/icons-material/LoginRounded'; +import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded'; +import AssignmentIndRoundedIcon from '@mui/icons-material/AssignmentIndRounded'; +import { + generateVerificationCode, + registerUser, + getVerificationStatus, +} from '../api'; +import QRCodeStyling from 'qr-code-styling'; +import popalogo from '../../../assets/icons/popa-popa.svg'; + +const ColorlibConnector = styled(StepConnector)(({ theme }) => ({ + [`&.${stepConnectorClasses.alternativeLabel}`]: { + top: 22, + }, + [`&.${stepConnectorClasses.active}`]: { + [`& .${stepConnectorClasses.line}`]: { + backgroundImage: + 'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)', + }, + }, + [`&.${stepConnectorClasses.completed}`]: { + [`& .${stepConnectorClasses.line}`]: { + backgroundImage: + 'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)', + }, + }, + [`& .${stepConnectorClasses.line}`]: { + height: 3, + border: 0, + backgroundColor: '#eaeaf0', + borderRadius: 1, + ...theme.applyStyles('dark', { + backgroundColor: theme.palette.grey[800], + }), + }, +})); + +const ColorlibStepIconRoot = styled('div')<{ + ownerState: { completed?: boolean; active?: boolean }; +}>(({ theme }) => ({ + backgroundColor: '#ccc', + zIndex: 1, + color: '#fff', + width: 50, + height: 50, + display: 'flex', + borderRadius: '50%', + justifyContent: 'center', + alignItems: 'center', + ...theme.applyStyles('dark', { + backgroundColor: theme.palette.grey[700], + }), + variants: [ + { + props: ({ ownerState }) => ownerState.active, + style: { + backgroundImage: + 'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)', + boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)', + }, + }, + { + props: ({ ownerState }) => ownerState.completed, + style: { + backgroundImage: + 'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)', + }, + }, + ], +})); + +function ColorlibStepIcon(props: StepIconProps) { + const { active, completed, className } = props; + + const icons: { [index: string]: React.ReactElement } = { + 1: , + 2: , + 3: , + }; + + return ( + + {icons[String(props.icon)]} + + ); +} + +const qrCode = new QRCodeStyling({ + width: 300, + height: 300, + // image: popalogo, + shape: 'square', + margin: 10, + dotsOptions: { + gradient: { + type: 'linear', + colorStops: [ + { + offset: 0, + color: 'rgb(242,113,33)', + }, + { + offset: 1, + color: 'rgb(233,64,87)', + }, + ], + }, + type: 'rounded', + }, + imageOptions: { + crossOrigin: 'anonymous', + margin: 20, + imageSize: 0.5, + }, + backgroundOptions: { + color: 'transparent', + }, +}); + export const Registration = () => { - return
Registration
; + const [activeStep, setActiveStep] = useState(0); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [open, setOpen] = useState(false); + const [message, setMessage] = useState(''); + const [verificationCode, setVerificationCode] = useState(null); + const ref = useRef(null); + const [url, setUrl] = useState(''); + const steps = ['Создание аккаунта', 'Верификация аккаунта в телеграмме']; + + useEffect(() => { + if (ref.current) { + qrCode.append(ref.current); + } + }, []); + + useEffect(() => { + qrCode.update({ + data: url, + }); + }, [url]); + + const handleCreateAccount = async () => { + const response = await registerUser(username, password); + if (response.status === 'success') { + setActiveStep(1); + } else { + setOpen(true); + setMessage(response.status); + } + }; + + const handleClose = () => { + setOpen(false); + }; + + useEffect(() => { + if (activeStep === 1) { + handleGenerateVerificationCode(username); + setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`); + + const intervalId = setInterval(() => { + handleVerifyCode(); + }, 5000); + + return () => { + clearInterval(intervalId); + }; + } + }, [activeStep]); + + const handleGenerateVerificationCode = async (username: string) => { + console.log(username); + const response = await generateVerificationCode(username); + setVerificationCode(response.code); + }; + + const handleVerifyCode = async () => { + const response = await getVerificationStatus(username); + if (response.is_verified) { + window.location.href = '/login'; + } + }; + + const handleOpenBot = () => { + window.open(`https://t.me/popa_popa_popa_bot?start=${username}`, '_blank'); + }; + + return ( + + } + > + {steps.map((label) => ( + + + {label} + + + ))} + + {activeStep === 0 && ( + + Создание аккаунта + Введите ваш никнейм + setUsername(e.target.value)} + sx={{ + width: '100%', + // '& .MuiFormLabel-root': { + // color: 'white', + // }, + '& .MuiInputBase-input': { + color: 'white', + }, + '& .MuiInput-underline:after': { + borderBottomColor: '#B2BAC2', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: '#E0E3E7', + color: 'white', + }, + '&:hover fieldset': { + borderColor: '#B2BAC2', + }, + '&.Mui-focused fieldset': { + borderColor: '#6F7E8C', + }, + }, + }} + /> + Введите ваш пароль + setPassword(e.target.value)} + sx={{ + width: '100%', + // '& .MuiFormLabel-root': { + // color: 'white', + // }, + '& .MuiInputBase-input': { + color: 'white', + }, + '& .MuiInput-underline:after': { + borderBottomColor: '#B2BAC2', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: '#E0E3E7', + color: 'white', + }, + '&:hover fieldset': { + borderColor: '#B2BAC2', + }, + '&.Mui-focused fieldset': { + borderColor: '#6F7E8C', + }, + }, + }} + /> + + + )} + {activeStep === 1 && ( + + Откройте бота в телеграмме + +
+ + Введите код верификации в боте + + {verificationCode ? ( + <> + + {verificationCode} + + Ждем ответа от бота + + + ) : ( + + )} + + )} + + + ); };