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}
+
+ Ждем ответа от бота
+
+ >
+ ) : (
+
+ )}
+
+ )}
+
+
+ );
};