diff --git a/assets/icons/popa-popa.png b/assets/icons/popa-popa.png new file mode 100644 index 0000000..6dd5b8c Binary files /dev/null and b/assets/icons/popa-popa.png differ 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} + + Ждем ответа от бота + + + ) : ( + + )} + + )} + + + ); };