Files
popa-launcher/src/renderer/pages/Registration.tsx
2025-12-03 10:58:47 +05:00

446 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import GradientTextField from '../components/GradientTextField';
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%)',
'linear-gradient( 95deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)'
},
},
[`&.${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,
backgroundImage: 'linear-gradient( 275deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)',
borderRadius: 1,
transition: 'background-image 1s ease, background-color 1s ease',
...theme.applyStyles('dark', {
backgroundColor: theme.palette.grey[800],
}),
},
}));
const ColorlibStepIconRoot = styled('div')<{
ownerState: { completed?: boolean; active?: boolean };
}>(({ theme }) => ({
backgroundColor: '#adadad',
zIndex: 1,
color: '#fff',
width: 50,
height: 50,
display: 'flex',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
transition: 'background-image 1s ease, box-shadow 1s ease, transform 1s ease',
...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)',
transform: 'scale(1.08)',
},
},
{
props: ({ ownerState }) => ownerState.completed,
style: {
backgroundImage:
'#adadad',
},
},
],
}));
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,
data: 'https://t.me/popa_popa_popa_bot?start=test',
shape: 'square',
margin: 10,
dotsOptions: {
gradient: {
type: 'linear',
colorStops: [
{
offset: 0,
color: 'rgb(242,113,33)',
},
{
offset: 1,
color: 'rgb(233,64,87)',
},
],
},
type: 'extra-rounded',
},
imageOptions: {
crossOrigin: 'anonymous',
margin: 20,
imageSize: 0.5,
},
backgroundOptions: {
color: 'transparent',
},
});
export const Registration = () => {
const [activeStep, setActiveStep] = useState(0);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [enterpassword, setEnterPassword] = 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 () => {
// простая валидация на фронте
if (!username || !password || !enterpassword) {
setOpen(true);
setMessage('Заполните все поля');
return;
}
if (password !== enterpassword) {
setOpen(true);
setMessage('Пароли не совпадают');
return;
}
// тут уже точно всё ок — отправляем запрос
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}`);
while (ref.current.firstChild) {
ref.current.removeChild(ref.current.firstChild);
}
const newQrCode = new QRCodeStyling({
width: 300,
height: 300,
image: popalogo,
data: 'https://t.me/popa_popa_popa_bot?start=test',
shape: 'square',
margin: 10,
dotsOptions: {
gradient: {
type: 'linear',
colorStops: [
{
offset: 0,
color: 'rgb(242,113,33)',
},
{
offset: 1,
color: 'rgb(233,64,87)',
},
],
},
type: 'extra-rounded',
},
imageOptions: {
crossOrigin: 'anonymous',
margin: 20,
imageSize: 0.5,
},
backgroundOptions: {
color: 'transparent',
},
});
newQrCode.update({
data: `https://t.me/popa_popa_popa_bot?start=${username}`,
});
setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`);
newQrCode.append(ref.current);
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 (
<>
<Stepper
activeStep={activeStep}
alternativeLabel
connector={<ColorlibConnector />}
sx={{
position: 'absolute',
top: '10%',
}} // чтобы отделить степпер от формы
>
{steps.map((label) => (
<Step key={label}>
<StepLabel
sx={{
'& .MuiStepLabel-label': {
color: '#adadad !important',
transition: 'color 1s ease',
},
'& .Mui-completed': {
color: '#adadad !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',
transition: 'all 1s ease',
},
}}
StepIconComponent={ColorlibStepIcon}
>
{label}
</StepLabel>
</Step>
))}
</Stepper>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '50vw', mt: activeStep === 1 ? '20%' : '0%' }}>
{activeStep === 0 && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<GradientTextField
label="Никнейм"
required
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<GradientTextField
label="Пароль"
required
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<GradientTextField
label="Подтвердите пароль"
required
name="enterpassword"
type="password"
value={enterpassword}
onChange={(e) => setEnterPassword(e.target.value)}
error={Boolean(enterpassword) && password !== enterpassword}
helperText={
Boolean(enterpassword) && password !== enterpassword
? 'Пароли не совпадают'
: ''
}
sx={{ mb: '0vw' }}
/>
<Button
variant="contained"
color="primary"
sx={{
transition: 'transform 0.3s ease',
width: '60%',
mt: 2,
background: 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
fontFamily: 'Benzin-Bold',
borderRadius: '2.5vw',
fontSize: '2vw',
'&:hover': {
transform: 'scale(1.1)',
},
}}
onClick={handleCreateAccount}
>
Создать
</Button>
</Box>
)}
{activeStep === 1 && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
alignItems: 'center',
}}
>
<Button
variant="contained"
color="primary"
sx={{
transition: 'transform 0.3s ease',
width: '60%',
mt: 2,
background: 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
fontFamily: 'Benzin-Bold',
borderRadius: '2.5vw',
fontSize: '2vw',
'&:hover': {
transform: 'scale(1.1)',
},
}}
onClick={handleOpenBot}
>
Открыть бота
</Button>
<div
ref={ref}
style={{
minHeight: 300,
}}
/>
<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>
</>
);
};