add: registration page
This commit is contained in:
BIN
assets/icons/popa-popa.png
Normal file
BIN
assets/icons/popa-popa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
18
assets/icons/popa-popa.svg
Normal file
18
assets/icons/popa-popa.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 80 KiB |
19
package-lock.json
generated
19
package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"find-java-home": "^2.0.0",
|
"find-java-home": "^2.0.0",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
"qr-code-styling": "^1.9.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
@ -18136,6 +18137,24 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/qs": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
"find-java-home": "^2.0.0",
|
"find-java-home": "^2.0.0",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
"qr-code-styling": "^1.9.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
|
@ -169,6 +169,66 @@ export interface OperationsResponse {
|
|||||||
operations: MarketplaceOperation[];
|
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<VerificationStatusResponse> {
|
||||||
|
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<GenerateVerificationCodeResponse> {
|
||||||
|
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<RegisterUserResponse> {
|
||||||
|
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(
|
export async function getPlayerInventory(
|
||||||
request_id: string,
|
request_id: string,
|
||||||
): Promise<PlayerInventory> {
|
): Promise<PlayerInventory> {
|
||||||
|
@ -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<unknown> } = {
|
||||||
|
1: <AssignmentIndRoundedIcon />,
|
||||||
|
2: <VerifiedRoundedIcon />,
|
||||||
|
3: <LoginRoundedIcon />,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorlibStepIconRoot
|
||||||
|
ownerState={{ completed, active }}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{icons[String(props.icon)]}
|
||||||
|
</ColorlibStepIconRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = () => {
|
export const Registration = () => {
|
||||||
return <div>Registration</div>;
|
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<string | null>(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 (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
<Stepper
|
||||||
|
activeStep={activeStep}
|
||||||
|
alternativeLabel
|
||||||
|
connector={<ColorlibConnector />}
|
||||||
|
>
|
||||||
|
{steps.map((label) => (
|
||||||
|
<Step key={label}>
|
||||||
|
<StepLabel
|
||||||
|
sx={{
|
||||||
|
'& .MuiStepLabel-label': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
'& .Mui-completed': {
|
||||||
|
color: 'white !important',
|
||||||
|
},
|
||||||
|
'& .Mui-active': {
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
StepIconComponent={ColorlibStepIcon}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</StepLabel>
|
||||||
|
</Step>
|
||||||
|
))}
|
||||||
|
</Stepper>
|
||||||
|
{activeStep === 0 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6">Создание аккаунта</Typography>
|
||||||
|
<Typography variant="body1">Введите ваш никнейм</Typography>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
name="username"
|
||||||
|
variant="outlined"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="body1">Введите ваш пароль</Typography>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ width: '100%', mt: 2 }}
|
||||||
|
onClick={handleCreateAccount}
|
||||||
|
>
|
||||||
|
Создать
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{activeStep === 1 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6">Откройте бота в телеграмме</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ width: '100%', mt: 2 }}
|
||||||
|
onClick={handleOpenBot}
|
||||||
|
>
|
||||||
|
Открыть бота
|
||||||
|
</Button>
|
||||||
|
<div ref={ref} />
|
||||||
|
<Typography variant="body1">
|
||||||
|
Введите код верификации в боте
|
||||||
|
</Typography>
|
||||||
|
{verificationCode ? (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{verificationCode}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">Ждем ответа от бота</Typography>
|
||||||
|
<CircularProgress />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<CircularProgress />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Snackbar
|
||||||
|
open={open}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={handleClose}
|
||||||
|
message={message}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user