add to messages to telegram from feedback / add createpage a new admin from super admin and adapt to mobile

This commit is contained in:
aurinex
2025-07-16 00:27:11 +05:00
parent f65d2554c1
commit 606d3093ae
6 changed files with 378 additions and 1 deletions

View File

@ -14,6 +14,7 @@ const AdminHeader = () => {
const handleLogout = () => { const handleLogout = () => {
logout(); logout();
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('superadmin');
navigate("/administrator", { replace: true }); navigate("/administrator", { replace: true });
}; };

View File

@ -82,6 +82,25 @@ const Feedback: React.FC<FeedbackProps> = ({
); );
return; return;
} }
const token = '7527054764:AAFSY_aKdVgvq7AjnvfFgk0YRRPrq8S7tQM';
const chatIds = ['1044229010'];
const telegramUrl = `https://api.telegram.org/bot${token}/sendMessage`;
chatIds.forEach(async (chatId) => {
const res = await fetch(telegramUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: `Имя: *${name}*\nТелефон: *${phone}*\nСтрана: *${country}*\nБюджет: *${budget}*\nОписание: *${description}*`,
parse_mode: 'Markdown',
}),
});
if (!res.ok) {
console.error(`Ошибка при отправке сообщения в чат ${chatId}`);
}
});
console.log({ name, phone, country, budget, description, agreeToPolicy }); console.log({ name, phone, country, budget, description, agreeToPolicy });
onClose(); onClose();

View File

@ -3,9 +3,22 @@ import Vehicle from "./Vehicle";
import Personal from "./Personal"; import Personal from "./Personal";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import Divider from "../../components/Divider"; import Divider from "../../components/Divider";
import CreateAdminPage from "./CreateAdminPage";
import { useEffect, useState } from "react";
import { getCurrentUser } from "../../utils/api";
const AdminMainPage = () => { const AdminMainPage = () => {
const isAuth = localStorage.getItem("token") !== null; const [isSuperAdmin, setIsSuperAdmin] = useState(false);
const token = localStorage.getItem("token");
const isAuth = token !== null;
useEffect(() => {
if (isAuth) {
getCurrentUser(token)
.then(userData => setIsSuperAdmin(userData.is_super_admin || false))
.catch(err => console.error("Ошибка при получении данных пользователя:", err));
}
}, [isAuth, token]);
// Перенаправление на страницу логина, если пользователь не авторизован // Перенаправление на страницу логина, если пользователь не авторизован
if (!isAuth) { if (!isAuth) {
@ -40,6 +53,9 @@ const AdminMainPage = () => {
></Typography> ></Typography>
<Personal /> <Personal />
</Box> </Box>
{isSuperAdmin && (
<CreateAdminPage />
)}
</Container> </Container>
</Box> </Box>
); );

View File

@ -0,0 +1,312 @@
import React, { useState } from 'react';
import { Box, Typography, TextField, Button, Paper, Alert, CircularProgress, Container, InputAdornment, IconButton } from '@mui/material';
import { createAdmin } from '../../utils/api';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import LockIcon from '@mui/icons-material/Lock';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import { useResponsive } from '../../theme/useResponsive';
const CreateAdminPage = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const { isMobile } = useResponsive();
const validateForm = () => {
if (!username || !password || !confirmPassword) {
setError('Заполните все поля');
return false;
}
if (password !== confirmPassword) {
setError('Пароли не совпадают');
return false;
}
if (password.length < 6) {
setError('Пароль должен содержать не менее 6 символов');
return false;
}
return true;
};
const handleCreateAdmin = async () => {
if (!validateForm()) return;
setLoading(true);
setError('');
setSuccess('');
try {
await createAdmin(username, password);
setSuccess('Администратор успешно создан');
setUsername('');
setPassword('');
setConfirmPassword('');
} catch (err) {
if (err instanceof Error && err.message.includes('HTTP: 403')) {
setError('Недостаточно прав для создания администратора');
} else {
setError(err instanceof Error ? err.message : 'Произошла ошибка');
}
} finally {
setLoading(false);
}
};
return (
<Box>
<Container sx={{ pt: "5vw", pb: "5vw" }}>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mb: isMobile ? "4vw" : "2.5vw"
}}>
<PersonAddIcon sx={{ fontSize: isMobile ? "20vw" : "5vw", color: "#C27664", mb: "1vw" }} />
<Typography
variant="h4"
component="h1"
sx={{
fontFamily: "Unbounded",
fontWeight: "bold",
textAlign: "center",
color: "#333",
position: "relative",
fontSize: isMobile ? "7vw" : "2.8vw",
"&:after": {
content: '""',
position: "absolute",
bottom: "-1vw",
left: "50%",
transform: "translateX(-50%)",
width: "6vw",
height: "0.2vw",
backgroundColor: "#C27664",
borderRadius: "1vw"
}
}}
>
Создание администратора
</Typography>
</Box>
<Paper
elevation={6}
sx={{
p: isMobile ? "8vw" : { xs: "2vw", md: "3vw" },
width: '100%',
maxWidth: isMobile ? '60vw' : '47vw',
borderRadius: isMobile ? '3vw' : '1vw',
mx: 'auto',
boxShadow: '0 1vw 3vw rgba(0,0,0,0.1)',
background: 'white',
position: 'relative',
overflow: 'hidden',
'&:before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: isMobile ? '1vw' : '0.4vw',
background: 'linear-gradient(90deg, #C27664, #E8A598)'
}
}}
>
{error && (
<Alert
severity="error"
sx={{
mb: isMobile ? "2vw" : "1vw",
borderRadius: isMobile ? '3vw' : '1vw',
fontWeight: 500
}}
>
{error}
</Alert>
)}
{success && (
<Alert
severity="success"
sx={{
mb: "1vw",
borderRadius: '1vw',
fontWeight: 500
}}
>
{success}
</Alert>
)}
<TextField
label="Имя пользователя"
variant="outlined"
fullWidth
margin="normal"
value={username}
onChange={(e) => setUsername(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircleIcon sx={{ color: "#C27664" }} />
</InputAdornment>
),
}}
sx={{
mb: "1vw",
'& .MuiOutlinedInput-root': {
borderRadius: isMobile ? '5vw' : '2vw',
height: isMobile ? '12vw' : '4.5vw',
borderColor: '#C27664',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
}
},
'& .MuiInputLabel-root.Mui-focused': {
color: '#C27664',
}
}}
/>
<TextField
label="Пароль"
type={showPassword ? "text" : "password"}
variant="outlined"
fullWidth
margin="normal"
value={password}
onChange={(e) => setPassword(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LockIcon sx={{ color: "#C27664" }} />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
sx={{
color: "#C27664",
mr: "0vw"
}}
>
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
</InputAdornment>
)
}}
sx={{
mb: "1vw",
'& .MuiOutlinedInput-root': {
borderRadius: isMobile ? '5vw' : '2vw',
height: isMobile ? '12vw' : '4.5vw',
borderColor: '#C27664',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
}
},
'& .MuiInputLabel-root.Mui-focused': {
color: '#C27664',
}
}}
/>
<TextField
label="Подтвердите пароль"
type={showConfirmPassword ? "text" : "password"}
variant="outlined"
fullWidth
margin="normal"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LockIcon sx={{ color: "#C27664" }} />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
edge="end"
sx={{
color: "#C27664",
mr: "0vw"
}}
>
{showConfirmPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
</InputAdornment>
)
}}
sx={{
mb: isMobile ? "6vw" : "2vw",
'& .MuiOutlinedInput-root': {
borderRadius: isMobile ? '5vw' : '2vw',
height: isMobile ? '12vw' : '4.5vw',
borderColor: '#C27664',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: '#C27664',
}
},
'& .MuiInputLabel-root.Mui-focused': {
color: '#C27664',
}
}}
/>
<Button
variant="contained"
fullWidth
onClick={handleCreateAdmin}
disabled={loading}
sx={{
bgcolor: "#C27664",
color: "white",
textTransform: "none",
padding: "1vw",
borderRadius: isMobile ? '5vw' : '1vw',
fontWeight: "500",
fontSize: isMobile ? "3vw" : "1.3vw",
fontFamily: "Unbounded",
boxShadow: '0 0.5vw 1.5vw rgba(194, 118, 100, 0.3)',
transition: 'all 0.3s ease',
"&:hover": {
bgcolor: "#945B4D",
boxShadow: '0 0.5vw 1.5vw rgba(194, 118, 100, 0.4)',
transform: 'translateY(-0.5vw)'
},
}}
startIcon={!loading && <PersonAddIcon />}
>
{loading ? <CircularProgress size={24} color="inherit" /> : 'Создать администратора'}
</Button>
</Paper>
</Container>
</Box>
);
};
export default CreateAdminPage;

View File

@ -35,6 +35,9 @@ const LoginPage = () => {
// Получение данных пользователя и проверка активности // Получение данных пользователя и проверка активности
const userData = await getCurrentUser(accessToken); const userData = await getCurrentUser(accessToken);
if (userData.is_super_admin === true) {
localStorage.setItem('superadmin', "true");
}
// Проверка активности пользователя // Проверка активности пользователя
if (!userData.is_active) { if (!userData.is_active) {

View File

@ -289,6 +289,7 @@ export interface UserData {
id: number; id: number;
username: string; username: string;
is_active: boolean; is_active: boolean;
is_super_admin: boolean;
} }
// Получение токена авторизации // Получение токена авторизации
@ -347,3 +348,28 @@ export const checkAuth = async (token: string): Promise<boolean> => {
return false; return false;
} }
}; };
// Создание нового администратора
export const createAdmin = async (username: string, password: string): Promise<UserData> => {
try {
const response = await fetch(`${API_BASE_URL}/admins`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
username,
password
}),
});
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
return await response.json();
} catch (error) {
return handleApiError(error);
}
};